TweetFollow Us on Twitter

Programmer's Challenge

Volume Number: 18 (2002)
Issue Number: 9
Column Tag: Programmer's Challenge

Programmer's Challenge

by Bob Boonstra

PhotoMosaic

Elvis lives. Or so some people have been saying in the 25 years since the reported death of the King. This momentus anniversary would have escaped my notice, had it not been brought to my attention by my spouse. And she probably wouldn't have mentioned it had she not seen some television show that provided the inspiration for this month's Challenge.

It seems some Elvis devotee had discovered a collection of rarely seen images of the Hound Dog, and constructed a portrait of the King using tiny versions of those images. I recall once having put together a jigsaw puzzle of the space shuttle similarly constructed. Viewed from a distance, the mosaic of smaller images took on the appearance of the shuttle on the launch pad, or, in the case of the television show, the swiveling hips of Mr. Love Me Tender. Having recently completed scoring our Jigsaw Puzzle challenge, the two ideas came together in my mind to form this month's Challenge.

The prototype for the code you should write is:

void InitMosaic(
   short numElements,      
      /* number of pixmaps from which the mosaic should be created */
   const PixMapHandle element[]
      /* element[i] is a PixMapHandle to the i-th image used to construct the mosaic */
);
typedef struct MosaicPiece {
   short elementIndex;
      /* index into element[] of the image used to construct this piece of the mosaic */
   Rect elementRect;
      /* which portion of element[elementIndex] was used to construct this piece */
   Rect mosaicRect;
      /* which portion of desiredImage this piece is used to construct */
      /* elementRect and mosaicRect must be the same size */
} MosaicPiece;
void Mosaic(
   const PixMapHandle desiredImage,
      /* PixMapHandle populated with the desired image to be constructed */
   const Rect minPieceSize,
      /* mosaic pieces must be of this size or larger */
   PixMapHandle mosaic,
      /* PixMapHandle to preallocated image in which the mosaic is to be placed */
      /* initialized to black */
   MosaicPiece *piece,
      /* pointer to array of mosaic pieces */
      /* populated by your code */
   long *numMosaicPieces
      /* number of mosaic pieces created by your code */
);
void TermMosaic(void);
   /* deallocate any memory allocated by InitMosaic or multiple Mosaic calls */

Your InitMosaic routine will be given an array of numElements images (elements) from which you will be asked to construct a sequence of mosaics. The image elements are in the form of PixMaps. InitMosaic should perform any analysis on these images that you decide would be useful in the subsequent mosaic construction.

Next, your Mosaic routine will be called multiple times. In each call, you will be given another PixMap, the image (desiredImage) you are being asked to approximate with your mosaic, along with a Rect that defines the minimum size of the pieces of the mosaic you will construct. Your task is to create an array of MosaicPieces that, when combined, form a mosaic that looks something like the desiredImage. Each MosaicPiece identifies the image element being used to create the mosaic piece, the portion (elementRect) of the image being used, and the portion (mosaicRect) of the mosaic being constructed with this piece. The mosaicRect of one piece must not overlap that of another piece. You should return the number of MosaicPieces you create in numMosaicPieces, and you should also create your mosaic image in the mosaic PixMap. Memory for the mosaic PixMap, its constituent members, and for the MosaicPieces will be preallocated.

Your TermMosaic routine will be called once for each call to InitMosaic so that you can return any dynamically allocated memory.

Scoring will be based on execution time and on how close your mosaic image resembles the desired image. For each pixel in the mosaic, I'll calculate the root mean square distance in RGB space between the corresponding values of the mosaic and the desired image. Your raw score will be the sum of those distances over all image pixels. For each second of execution time, I'll add a penalty of 10% to the raw score. Execution time will include the time taken by the Mosaic routine, plus a share of the InitMosaic and TermMosaic time, divided equally among the Mosaic calls. The winner will be the correct solution with the lowest score.

This will be a native PowerPC Carbon C++ Challenge, using the Metrowerks CodeWarrior Pro 7.0 development environment. Please be certain that your code is carbonized, as I may evaluate this Challenge using Mac OS X.

Winner of the June, 2002 Challenge

The June Challenge invited readers to write a player for a modified Matchsticks game. The original game is played by removing one or more matchsticks from a triangular grid, with the object being to force your opponent to take the last matchstick. We modified the game slightly, converting the board from a triangle to a rectangle, allowing matchsticks to be removed from columns as well as rows, leaving some "holes" in the initial array of matchsticks, restricting move possibilities to sequences of consecutive matchsticks, and redefining options for victory to include taking the last matchstick, as well as forcing your opponent to do so.

Unfortunately, we only had one entry for this Challenge, so the tournament to determine the winner was brief. Congratulations to Robin Landsbert for submitting the winning entry. The perceptive reader will recall that Robin was the one who suggested using the Matchstick game as a Challenge problem. This led to some questions about eligibility to participate and fairness of accepting this entry. Since the actual Challenge was significantly different than the one originally suggested, Robin really had no advantage, so I decided to accept the entry.

Robin's code doesn't include much commentary about strategy, so a little explanation is in order. The initialization routine copies the initial board to a "wide" board with a sentinal at the beginning and end of each row. Game strategy is organized around the number of "islands", or sequences of matchsticks, that remain on the board. Game play is based on maintaining the correct parity on the number of these islands, depending on whether the objective is to take or to avoid taking the last matchstick. If the parity is correct, the code takes one island to maintain parity; if it is not, the code attempts to create the right number of new islands to reset the parity. Routine Create1or3Islands attempts to create an odd number of islands to preserve a winning position, while Create2or4Islands tries to maintain parity to reverse a losing position. The diagrams in the Create1or3Islands routine are probably the best way to understand the island creation approach. Aprat from the island count, the code does not attempt to anticipate the play of the opponent. All that said, without having other solutions to test the code against, it is difficult to determine how effective this strategy is...

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.

   
Rank    Name              Points     Wins       Total
                          (24 mo)    (24 mo)    Points
                          
 1.   Munter, Ernst          243       8         872
 2.   Saxton, Tom             65       2         230
 3.   Taylor, Jonathan        57       2          83
 4.   Stenger, Allen          53       1         118
 5.   Rieken, Willeke         42       2         134
 6.   Wihlborg, Claes         40       2          49
 8.   Landsbert, Robin        22       1          22
 9.   Gregg, Xan              20       1         140
10.   Mallett, Jeff           20       1         114
11.   Cooper, Tony            20       1          20

Here is Robin's winning MatchSticks solution. The code had been abridged for publication because of page constraints; see http://www.mactech.com for the full version.

    MatchSticks.cp

    Copyright (c) 2002

    Robin Landsbert

#include "Matchsticks.h"
#ifndef DEBUGGING
   #define DEBUGGING !(__option(peephole))
#endif
#if DEBUGGING
   static void Assert_ (bool inTest)
      {
      if (!inTest)
         {
         long x = 0; // just so I can get a break point here
         }
      }
#else
   #define Assert_(x)
   #if __profile__
      #pragma inline_depth(0)
   #else
      #pragma inline_depth(8)
   #endif
#endif
Global declarations
static long      gDimension;                     
   // the width an height of the board in matches
static long      gDimensionMinus1;               
   // one less than the width used in QuickTakeSurroundedMatch
static long      gWideDimension;                  
   // the width an height of the board with one extra blank 
   // space at the start and end of the board
static long      g2WideDimension;               
   // gWideDimension + gWideDimension, used for jumping from 
   // north match to south match quickly
static long      gNumMatches;                  
   // number of matches left
static long      gCriticalCount;                  
   // the number of matches above which the game has no 
   // predetermined outcome so no need to analyse move
static long      gNumIslands;                  
   // the current number of islands
static char*   gBoard;                        
   // pointer to the board of matches
static char*   gLastMatch;                     
   // pointer to the last match when looking backwards
static char*   gWideBoard;                     
   // pointer to my wide board of matches
static char*   gWideBoardStart;               
   // pointer to the first match on my wide board of matches
static char*   gWideBoardStartJumpToFirstMatch;   
   // pointer to the first fully surrounded match on my wide board of matches
static char*   gIslandSaver;                  
   // pointer to where islands were during taking moves so they can 
   // be put back if move is undone
static bool      gLastMatchLoses;               
   // if I take the last match I lose
static bool      gGameOver;                     
   // the games outcome if fully determined, as there are only islands left
static bool      gDoWideMove;                  
   // I already did the move on the wide board during my analysis, 
   // so I do not need to do it again
static bool      gSetIslandSaver;
enum TDirection
   {
   eUnknownDirection,
   eNorth,
   eSouth,
   eEast,
   eWest
   };
IsThisANewIsland
inline bool IsThisANewIsland (register char* cell)
   {
   if (!(*cell))
      return false;
   Assert_(*cell != 2);
   register char* nextMatch = cell - 1;
   if (*nextMatch)
      return false;
   nextMatch += 2;
   if (*nextMatch)
      return false;
   nextMatch = cell - gWideDimension;
   if (*nextMatch)
      return false;
   nextMatch += g2WideDimension;
   if (*nextMatch)
      return false;
   return true; // new island
   }
IsThisANewIslandAndSetIslands
IsThisANewIslandAndSetIslands
inline bool IsThisANewIslandAndSetIslands (register char* cell) 
   // marks any new islands as value 2 so can be detected again quickly
   {
   if (!(*cell))
      return false;
   Assert_(*cell != 2);
   register char* nextMatch = cell - 1;
   if (*nextMatch)
      return false;
   nextMatch += 2;
   if (*nextMatch)
      return false;
   nextMatch = cell - gWideDimension;
   if (*nextMatch)
      return false;
   nextMatch += g2WideDimension;
   if (*nextMatch)
      return false;
   *cell = 2; // mark as an island
   return true; // new island
   }
LookAtCellsAroundThisGoingEast
inline long LookAtCellsAroundThisGoingEast (register char* cell)
// omitted for brevity in publication, see online archive
LookAtCellsAroundThisGoingEastAndSetIslands
inline long LookAtCellsAroundThisGoingEastAndSetIslands (
   register char* cell)
// omitted for brevity in publication, see online archive
LookAtCellsAroundThisGoingSouth
inline long LookAtCellsAroundThisGoingSouth (register char* cell)
   {
   --cell; // look at the cells east and west
   long count = IsThisANewIsland (cell);
   cell += 2;
   if (IsThisANewIsland (cell))
      ++count; // new island
   return count;
   }
LookAtCellsAroundThisGoingSouthAndSetIslands
inline long LookAtCellsAroundThisGoingSouthAndSetIslands (register char* cell)
// omitted for brevity in publication, see online archive
CountIslandsQuick
inline long CountIslandsQuick (bool& outOnlyIslands, bool playingRow, short rowOrColumnNumber, 
short startingColOrRow, short endingColOrRow)

   {
   // only call this after I have removed the matches for testing
   register char* cell;
   register long islandCount = gNumIslands; 
      // this is how many islands there were at last count
   register long count = endingColOrRow - startingColOrRow + 1;
      // this is how many matches he took
   if (playingRow) // going east
      {
      cell = gWideBoardStart + (rowOrColumnNumber * gWideDimension) 
               + startingColOrRow - 1;
      islandCount += IsThisANewIslandAndSetIslands (cell);
      ++cell;
      while (count)
         {
         islandCount += LookAtCellsAroundThisGoingEast (cell);
         ++cell;
         --count;
         }
      }
   else
      {
      cell = gWideBoardStart + rowOrColumnNumber + 
            (startingColOrRow * gWideDimension) - gWideDimension;
      islandCount += IsThisANewIslandAndSetIslands (cell);
      cell += gWideDimension;
      while (count)
         {
         islandCount += LookAtCellsAroundThisGoingSouth (cell);
         cell += gWideDimension;
         --count;
         }
      }
   islandCount += IsThisANewIslandAndSetIslands (cell);
   outOnlyIslands = (islandCount == gNumMatches); 
      // return whether there are only discrete matchsticks
   return islandCount; // return how many discrete matchsticks there are
   }
CountIslands
inline void CountIslands (bool playingRow, 
   short rowOrColumnNumber, short startingColOrRow, 
   short endingColOrRow)
   {
   // only call this after I have removed the matches
// omitted for brevity in publication, see online archive
DoWideMoveAndCountIslands
inline void DoWideMoveAndCountIslands (bool playingRow, 
      short rowOrColumnNumber, short startingColOrRow, 
      short endingColOrRow)
   {
   register char* cell;
   register long islandCount = gNumIslands;
   register long count = endingColOrRow - startingColOrRow + 1; 
      // this is how many matches he took
   gNumMatches -= count;
   if (playingRow) // going east
      {
      cell = gWideBoardStart + (rowOrColumnNumber * gWideDimension) 
                              + startingColOrRow;
      if (*cell == 2)
         --gNumIslands;
      *cell = 0; 
         // need to set this cell to zero first as it may create an island before it
      --cell;
      islandCount += IsThisANewIslandAndSetIslands (cell);
      ++cell;
      while (count)
         {
         if (*cell == 2)
            --gNumIslands;
         *cell = 0;
         islandCount += 
            LookAtCellsAroundThisGoingEastAndSetIslands (cell);
         ++cell;
         --count;
         }
      }
   else
      {
      cell = gWideBoardStart + rowOrColumnNumber + 
         (startingColOrRow * gWideDimension);
      if (*cell == 2)
         --gNumIslands;
      *cell = 0; 
         // need to set this cell to zero first as it may create an island above it
      cell -= gWideDimension;
      islandCount += IsThisANewIslandAndSetIslands (cell);
      cell += gWideDimension;
      while (count)
         {
         if (*cell == 2)
            --gNumIslands;
         *cell = 0;
         islandCount += 
               LookAtCellsAroundThisGoingSouthAndSetIslands (cell);
         cell += gWideDimension;
         --count;
         }
      }
   islandCount += IsThisANewIslandAndSetIslands (cell);
   gNumIslands = islandCount;
   }
TakeAnIsland
inline bool TakeAnIsland (short& outRow, short& outCol)
   {
   if (gNumIslands)
      {
      register char* match = gWideBoardStart;   
      for (register long i = 0; i < gDimension; ++i)
         {
         for (register long j = 0; j < gDimension; ++j)
            {
            if (*match == 2) // this is marked as an island
               {
               outRow = i;
               outCol = j;
               return true;
               }
            ++match;
            }
         // skip the last space at the end of the row and the next space at the 
         // start of the next row
         match += 2;
         }
      Assert_(false);
      }
   return false; // there are no Islands
   }
enum TWinningState // the lower the value the worse state I am in
   {
   eLost,
   eLosing,
   eUnknown,
   eWinning,
   eWon
   };
GetMoveState
inline TWinningState GetMoveState (bool outPlayingRow, 
               short outRowOrColumnNumber, short outStartingColOrRow, 
               short outEndingColOrRow)
   {
   bool winning;
   bool onlyIslands;
   // count how many island matches there are
   const long numIsland = CountIslandsQuick (onlyIslands, 
         outPlayingRow, outRowOrColumnNumber, outStartingColOrRow, 
         outEndingColOrRow);
   if (!numIsland)
      return eUnknown;
   if (gLastMatchLoses)
      winning = (numIsland & 0x00000001) != 0; 
         // if there are an odd number of islands I am currently winning
   else
      winning = (numIsland & 0x00000001) == 0; 
         // if there are an even number of islands I am currently winning
   if (onlyIslands) 
      // I've won or lost, there have no choice, so just take the first island
      {
      if (winning)
         {
         gGameOver = true;
         return eWon;
         }
      return eLost;
      }
   if (winning)
      return eWinning;
   return eLosing;
   }
DoMove
inline void DoMove (bool playingRow, short rowOrColumnNumber, short startingColOrRow, short 
endingColOrRow)

   {
   register char* cell;
   register long count = endingColOrRow - startingColOrRow + 1; 
      // this is how many matches I took
   if (playingRow)
      {
      cell = gBoard + (rowOrColumnNumber * gDimension) + 
                  startingColOrRow;
      while (count)
         {
         Assert_(*cell != 0);
         *cell = 0;
         ++cell;
         --count;
         }
      }
   else
      {
      cell = gBoard + rowOrColumnNumber + 
                     (startingColOrRow * gDimension);
      while (count)
         {
         Assert_(*cell != 0);
         *cell = 0;
         cell += gDimension;
         --count;
         }
      }
   }
DoWideMove
inline void DoWideMove (bool playingRow, short rowOrColumnNumber, 
               short startingColOrRow, short endingColOrRow)
   {
   register char* cell;
   register long count;
   char* p;
   if (startingColOrRow > endingColOrRow) 
      // taking backwards so swap them
      {
      count = startingColOrRow;
      startingColOrRow = endingColOrRow;
      endingColOrRow = count;
      }
   count = endingColOrRow - startingColOrRow + 1; 
      // this is how many matches he took
   gNumMatches -= count;
   if (playingRow)
      {
      cell = gWideBoardStart + (rowOrColumnNumber * gWideDimension) 
                        + startingColOrRow;
      while (count)
         {
         Assert_(*cell != 0);
         if (*cell == 2)
            {
            gSetIslandSaver = true;
            --gNumIslands;
            p = gIslandSaver + count;
            *p = 1; 
         // remember where the island was incase needs to be undone
            }
         *cell = 0;
         ++cell;
         --count;
         }
      }
   else
      {
      cell = gWideBoardStart + rowOrColumnNumber + 
                  (startingColOrRow * gWideDimension);
      while (count)
         {
         Assert_(*cell != 0);
         if (*cell == 2)
            {
            gSetIslandSaver = true;
            --gNumIslands;
            p = gIslandSaver + count;
            *p = 1; // remember where the island was incase needs to be undone
            }
         *cell = 0;
         cell += gWideDimension;
         --count;
         }
      }
   }
UndoWideMove
inline void UndoWideMove (bool playingRow, 
      short rowOrColumnNumber, short startingColOrRow, 
      short endingColOrRow)
// omitted for brevity in publication, see online archive
inline void TakeFirstMatch (short& outRow, short& outCol)
   {
   register char* match = gLastMatch; 
      // start at the last match as that is where they are most 
      // likely to be at the end of the game
   register long i = gDimension;
   while (i)
      {
      --i;
      register long j = gDimension;
      while (j)
         {
         --j;
         if (*match) // there is a matchstick here
            {
            outRow = i;
            outCol = j;
            return;
            }
         --match;
         }
      }
   Assert_(false);
   }
OKMove
inline bool OKMove (bool outPlayingRow, 
      short outRowOrColumnNumber, short outStartingColOrRow, 
      short outEndingColOrRow)
   {
   DoWideMove (outPlayingRow, outRowOrColumnNumber, outStartingColOrRow, outEndingColOrRow); 
      // apply my move to my wide board
   const TWinningState state = GetMoveState (outPlayingRow, 
      outRowOrColumnNumber, outStartingColOrRow, outEndingColOrRow);
   if (state > eLosing) 
      // I either do not know, or I am in a winning state, which is OK
      {
      gDoWideMove = false;
      return true;
      }
   // this move is no good so undo it
   UndoWideMove (outPlayingRow, outRowOrColumnNumber, 
            outStartingColOrRow, outEndingColOrRow);
   return false;
   }
TakeBestMatch
inline void TakeBestMatch (short& outRow, short& outCol)
   {
   long bestCount = -1;
   TWinningState bestState = eLost;
   register char* match = gWideBoardStart;
   
   for (long i = 0; i < gDimension; ++i)
      {
      for (register long j = 0; j < gDimension; ++j)
         {
         if (*match) // there is a matchstick here
            {
            if (*match != 2) // this is NOT an island
               {
               register long count;
               register char* test = match + gWideDimension; // south
               if (*test)
                  count = 1;
               else
                  count = 0;
               test -= g2WideDimension; // north
               if (*test)
                  ++count;
               test = match + 1; // east
               if (*test)
                  ++count;
               test -= 2; // west
               if (*test)
                  ++count;
               if (count == 4) // I'll take this one as it will make a void
                  {
                  // 0 0 0 0 0
                  // 0 0 1 0 0
                  // 0 1 x 1 0
                  // 0 0 1 0 0
                  // 0 0 0 0 0
                  if (OKMove (true, i, j, j)) 
                     // could be a bad move as taking x above leaving 4 
                     // islands when last latch loses
                     {
                     outRow = i;
                     outCol = j;
                     return;
                     }
                  }
               else
               if (count > bestCount)
                  {
                  DoWideMove (true, i, j, j); // apply my move to my wide board
                  TWinningState state = GetMoveState (true, i, j, j);
                  if (state >= eUnknown) 
                     // only do this if it puts me in a winning position
                     {
                     gDoWideMove = false;
                     outRow = i;
                     outCol = j;
                     return;
                     }
                  UndoWideMove (true, i, j, j);
                  if (state > bestState)
                     {
                     bestState = state;
                     bestCount = count;
                     outRow = i;
                     outCol = j;
                     }
                  }
               }
            }
         ++match;
         }
      // skip the last space at the end of the row and the next space at the 
      // start of the next row
      match += 2;
      }
   if (bestCount == -1) 
      // did not find a match, must be all islands (as they were marked as 2s)
      TakeFirstMatch (outRow, outCol);
   }
QuickTakeSurroundedMatch
inline void QuickTakeSurroundedMatch (short& outRow, short& outCol)
// omitted for brevity in publication, see online archive
TakeAllTheMatchesInThisDirection
inline void TakeAllTheMatchesInThisDirection (   TDirection inDir, 
   long i, long j, register char* match,
   bool& outPlayingRow, short& outRowOrColumnNumber,
   short& outStartingColOrRow, short& outEndingColOrRow)
// returns how many matches were taken
   {
   if (inDir == eEast)
      {
      outPlayingRow = true;
      outRowOrColumnNumber = i;
      outStartingColOrRow = j;
      outEndingColOrRow = outStartingColOrRow;
      ++match;
      while (*match) // keep looking east until hit a space
         {
         ++outEndingColOrRow;
         ++match;
         }
      }
   else
   if (inDir == eSouth)
// omitted for brevity in publication, see online archive
   else
   if (inDir == eWest)
// omitted for brevity in publication, see online archive
   else // must be north
// omitted for brevity in publication, see online archive
   }
Create1Or3Islands
inline bool Create1Or3Islands (bool& outPlayingRow, short& outRowOrColumnNumber, short& 
outStartingColOrRow, short& outEndingColOrRow)

   {
   register char* match = gWideBoardStart;
   register char* north;
   register char* south;
   register char* west;
   register char* east;
   register long i;
   register long j;
   for (i = 0; i < gDimension; ++i)
      {
      for (j = 0; j < gDimension; ++j)
         {
         if (*match) // there is a matchstick here
            {
            if (*match == 2) // this is an island
               goto next;
            // see how many matches surround this
            unsigned long count;
            south = match + gWideDimension;
            if (*south)
               count = 1;
            else
               count = 0;
            north = match - gWideDimension;
            if (*north)
               ++count;
            east = match + 1;
            if (*east)
               ++count;
            west = match - 1;
            if (*west)
               ++count;
            if (count == 1) 
               // matches only go in one direction. If I leave this match 
               // and take the rest in this direction I will leave an island
               goto special1MatchCase;
            if (count == 2) 
               // I can create another island if there is only one match 
               // next to me in one of the directions
               goto special2MatchCase;
            if (count == 4) // I can create 3 islands if there is only one 
               // match next to me in all of the directions
               goto special4MatchCase;
            }
      next:
         ++match;
         }
      match += 2; // skip blank space at the end of the row and at beginning of next row
      }
   return false;
   register char* nextMatch;
special4MatchCase: // this can create 3 islands, which is OK
   // X = this match
   // | = another match
   // ? = dont care whether there is a match or not
   // * = must be empty
   //
   // ? ? * ? ?
   // ? * | * ?
   // * | X | --> as may matches as you like
   // ? * | * ?
   // ? ? * ? ?
   // you can rotate the above setup in 4 directions
   TDirection dir = eUnknownDirection;
   nextMatch = north - gWideDimension;
   if (*nextMatch)
      dir = eNorth;
   nextMatch = south + gWideDimension;
   if (*nextMatch)
      {
      if (dir) // matches go off in two directions from this match which is no good
         goto next;
      dir = eSouth;
      }
   nextMatch = east + 1;
   if (*nextMatch)
      {
      if (dir)
         goto next;
      dir = eEast;
      }
   nextMatch = west - 1;
   if (*nextMatch)
      {
      if (dir)
         goto next;
      dir = eWest;
      }
   if (!dir) // happens with vertical cross shape surrounded entirely by white space
      dir = eEast; // so just take the east match
   // ? ? * ? ?
   // ? 0 | 0 ?
   // * | X | --> as may matches as you like
   // ? 0 | 0 ?
   // ? ? * ? ?
   // look for the matches marked zero above
   nextMatch = north - 1;
   if (*nextMatch)
      goto next;
   nextMatch += 2;
   if (*nextMatch)
      goto next;
   nextMatch = south - 1;
   if (*nextMatch)
      goto next;
   nextMatch += 2;
   if (*nextMatch)
      goto next;
   // we are ok
   TakeAllTheMatchesInThisDirection (dir, i, j, match, 
      outPlayingRow, outRowOrColumnNumber, outStartingColOrRow, 
      outEndingColOrRow);
   if (OKMove (outPlayingRow, outRowOrColumnNumber, 
               outStartingColOrRow, outEndingColOrRow)) 
      // check that taking this does not create more islands than I want
      return true;
   goto next;
   
special2MatchCase:
   // X = this match
   // | = another match
   // ? = dont care whether there is a match or not
   // * = must be empty   
   // Check two matches in opposite directions
   if (*north && *south) // otherwise it is just in the middle of a north-south line
      {
      // ? ^ ?
      // ? | ?
      // * X *
      // 1 | 2
      // ? 3 ?
      // or layout 2
      // ? 2 ?
      // 1 | 3
      // * X *
      // ? | ?
      // ? v ?
      nextMatch = south + gWideDimension;
      if (*nextMatch) // must be layout 2
         {
         nextMatch = north - 1; // test 1
         if (*nextMatch)
            goto next;
         nextMatch += 2; // test 2
         if (*nextMatch)
            goto next;
         nextMatch = north - gWideDimension; // test 3
         if (*nextMatch)
            goto next;
         // OK take all the match south from match
         TakeAllTheMatchesInThisDirection (eSouth, i, j, match, 
            outPlayingRow, outRowOrColumnNumber, outStartingColOrRow, 
            outEndingColOrRow);
         if (OKMove (outPlayingRow, outRowOrColumnNumber, 
                     outStartingColOrRow, outEndingColOrRow))
            return true;
         goto next;
         }
      else
         {
         nextMatch = south - 1; // test 1
         if (*nextMatch)
            goto next;
         nextMatch += 2; // test 2
         if (*nextMatch)
            goto next;
         nextMatch = south + gWideDimension; // test 3
         if (*nextMatch)
            goto next;
         // OK take all the match south from match
         TakeAllTheMatchesInThisDirection (eNorth, i, j, match, 
            outPlayingRow, outRowOrColumnNumber, outStartingColOrRow, 
            outEndingColOrRow);
         if (OKMove (outPlayingRow, outRowOrColumnNumber, 
                  outStartingColOrRow, outEndingColOrRow))
            return true;
         }
      goto next;
      }
   if (*east && *west) // otherwise it is just in the middle of an east-west line
      {
      //   ? 1 * ? ?
      //   3 | X | --> Take from match to the end of the row
      //   ? 2 * ? ?
      // or layout 2
      //   ? ? * 1 ?
      // <-- | X | 3
      //   ? ? * 2 ?
      
      nextMatch = west - 1;
      if (*nextMatch) // must be layout 2
         {
         nextMatch = east - gWideDimension; // test 1
         if (*nextMatch)
            goto next;
         nextMatch += g2WideDimension; // test 2
         if (*nextMatch)
            goto next;
         nextMatch = east + 1; // test 3
         if (*nextMatch)
            goto next;
         // OK take all the match south from match
         TakeAllTheMatchesInThisDirection (eWest, i, j, match,
            outPlayingRow, outRowOrColumnNumber, outStartingColOrRow, 
            outEndingColOrRow);
         if (OKMove (outPlayingRow, outRowOrColumnNumber, 
                  outStartingColOrRow, outEndingColOrRow))
            return true;
         goto next;
         }
      else // layout 1
         {
         nextMatch = west - gWideDimension; // test 1
         if (*nextMatch)
            goto next;
         nextMatch += g2WideDimension; // test 2
         if (*nextMatch)
            goto next;
         nextMatch = west - 1; // test 3
         if (*nextMatch)
            goto next;
         // OK take all the match south from match
         TakeAllTheMatchesInThisDirection (eEast, i, j, match, 
            outPlayingRow, outRowOrColumnNumber, outStartingColOrRow, 
            outEndingColOrRow);
         if (OKMove (outPlayingRow, outRowOrColumnNumber, 
                  outStartingColOrRow, outEndingColOrRow))
            return true;
         }
      goto next;
      }
   //     ? * ?
   //     * X | --> as may matches as you like
   //     3 | 2
   //     ? 1 ?
   // or
   //     ? * ?
   // <-- | X * as may matches as you like
   //     3 | 2
   //     ? 1 ?
   // you can rotate the above setup in 4 directions
   // and reflect it in 2 directions
   // making 8 cases
   if (*south) // other match MUST be East or West
      {
      nextMatch = south + gWideDimension;
      if (!(*nextMatch)) // good, it is blank in south direction, test 1
         {
         // east AND west from here have to be blank too
         nextMatch = south + 1; // east, test 2
         if (*nextMatch)
            goto next;
         nextMatch -= 2; // west, test 3
         if (*nextMatch)
            goto next;
         // found it
         // find the other direction and take all the matches from match in that direction
         if (*east)
            TakeAllTheMatchesInThisDirection (eEast, i, j, match, 
               outPlayingRow, outRowOrColumnNumber, outStartingColOrRow, 
               outEndingColOrRow);
         else // must be west
            TakeAllTheMatchesInThisDirection (eWest, i, j, match, 
               outPlayingRow, outRowOrColumnNumber, outStartingColOrRow, 
               outEndingColOrRow);
         if (OKMove (outPlayingRow, outRowOrColumnNumber, 
                     outStartingColOrRow, outEndingColOrRow))
            return true;
         goto next;
         }
      else // must be blank in east or west direction with south as the direction to take
         {
         //     ? ? * 2 ?
         //     ? * X | 1
         //     ? ? | 3 ?
         //     ? ? v ? ?
         // or
         //     ? 2 * ? ?
         //     1 | X * ?
         //     ? 3 | ? ?
         //     ? ? v ? ?
         if (*east)
            {
            nextMatch = east + 1; // test 1
            if (*nextMatch)
               goto next;
            nextMatch = east - gWideDimension; // test 2
            if (*nextMatch)
               goto next;
            nextMatch += g2WideDimension; // test 3
            if (*nextMatch)
               goto next;
            TakeAllTheMatchesInThisDirection (eSouth, i, j, match, 
               outPlayingRow, outRowOrColumnNumber, outStartingColOrRow, 
               outEndingColOrRow);
            if (OKMove (outPlayingRow, outRowOrColumnNumber, 
                     outStartingColOrRow, outEndingColOrRow))
               return true;
            goto next;
            }
         else // must be west, case 2
               // omitted for brevity in publication, see online archive
         }
      goto next;
      }
   //     ? 1 ?
   //     3 | 2
   //     * X | --> as may matches as you like
   //     ? * ?
   // or case 2
   //     ? 1 ?
   //     3 | 2
   //   <-- X * as may matches as you like
   //     ? * ?
   //if (*north) // no need to test
   // other match MUST be West or East
   // omitted for brevity in publication, see online archive
   goto next;
special1MatchCase:
   // just one direction of matches, leave this one alone but take all others in this 
   // direction to create a single matchstick surrounded by white space
   if (*south)
      {
      outPlayingRow = false;
      outRowOrColumnNumber = j;
      outStartingColOrRow = i + 1;
      outEndingColOrRow = outStartingColOrRow;
      south += gWideDimension;
      while (*south) // keep looking south until get a space
         {
         ++outEndingColOrRow;
         south += gWideDimension;
         }
      }
   else
   if (*east)
// omitted for brevity in publication, see online archive
   else
   if (*west)
// omitted for brevity in publication, see online archive
   else
   if (*north)
// omitted for brevity in publication, see online archive
   if (OKMove (outPlayingRow, outRowOrColumnNumber, 
         outStartingColOrRow, outEndingColOrRow))
      return true;
   goto next;
   }
Create2Or4Islands
inline bool Create2Or4Islands (bool& outPlayingRow, 
      short& outRowOrColumnNumber, short& outStartingColOrRow, 
      short& outEndingColOrRow)
// omitted for brevity in publication, see online archive
   }
TakeARowOrColumn
inline bool TakeARowOrColumn (bool& outPlayingRow, 
      short& outRowOrColumnNumber, short& outStartingColOrRow, 
      short& outEndingColOrRow)
   {
   register char* match = gWideBoardStart;
   register char* north;
   register char* south;
   register char* west;
   register char* east;
   register long i;
   register long j;
   for (i = 0; i < gDimension; ++i)
      {
      for (j = 0; j < gDimension; ++j)
         {
         if (*match) // there is a matchstick here
            {
            if (*match != 2) // this is NOT an island
               {
               // see how many matches surround this, I'm looking for one 
               // direction only to be able to take a row
               south = match + gWideDimension;
               north = match - gWideDimension;
               east = match + 1;
               west = match - 1;
               if (*south)
                  {
                  if (*north) // at least two matches surround this match
                     goto next;
                  if (*east)
                     goto next;
                  if (*west)
                     goto next;
                  // only south set
                  TakeAllTheMatchesInThisDirection (eSouth, i, j, match, 
                     outPlayingRow, outRowOrColumnNumber, 
                     outStartingColOrRow, outEndingColOrRow);
                  if (OKMove (outPlayingRow, outRowOrColumnNumber, 
                              outStartingColOrRow, outEndingColOrRow))
                     return true;
                  goto next;
                  }
               if (*east)
                  {
                  if (*west)
                     goto next;
                  if (*north)
                     goto next;
                  // only east set
                  TakeAllTheMatchesInThisDirection (eEast, i, j, match, 
                     outPlayingRow, outRowOrColumnNumber, 
                     outStartingColOrRow, outEndingColOrRow);
                  if (OKMove (outPlayingRow, outRowOrColumnNumber, 
                        outStartingColOrRow, outEndingColOrRow))
                     return true;
                  goto next;
                  }
               if (*west)
                  // omitted for brevity in publication, see online archive
               if (*north)
                  // omitted for brevity in publication, see online archive
                  }
               }
            }
      next:
         ++match;
         }
      match += 2; 
         // skip blank space at the end of the row and at beginning of next row
      }
   return false;
   }
InitMatchsticks
/*=========================================================*/
/* game is played on a square board of size dimension x dimension */
/* board[row*dimension + col] is board cell (row,col) */
/* board[] == 1 represents a matchstick, == 0 represents an empty cell */
/* true if you will play first in this game */
/* true if taking the last matchstick loses the game, 
      false if taking the last one wins the game */
/* identifier for your opponent in this game */
/*=========================================================*/
void InitMatchsticks (short dimension, const char* board, bool, 
               bool lastMatchstickLoses, short)
   {
   // copy the initial parameters into my globals
   gDimension = dimension;
   gDimensionMinus1 = dimension - 1;
   gBoard = (char*)board;
   gLastMatch = gBoard + (gDimension * gDimension) - 1;
   gLastMatchLoses = lastMatchstickLoses;
   gGameOver = false;
   gSetIslandSaver = false;
   
   gNumIslands = 0;
   gNumMatches = 0;
   gWideDimension = gDimension + 2; 
      // the wide board is bounded by a ring of empty positions
   g2WideDimension = gWideDimension + gWideDimension;
   long wideBoardSize = gWideDimension * gWideDimension;
      // make gWideDimension a multiple of 4 for speed
   wideBoardSize = ((wideBoardSize + 15) >> 4) << 4; 
      // make multiple of 16 so clears very quickly
   gWideBoard = ::NewPtrClear (wideBoardSize); // clear the entire board
   if (!gWideBoard)
      ::ExitToShell ();
   // this board has a row and column of empty spaces round it so 
   // I can always look east, west, north or south at the edge of the board
   gIslandSaver = ::NewPtrClear (gDimension); 
      // can only take a maximum of a whole row
   if (!gIslandSaver)
      ::ExitToShell ();
   
   const long widePlus1 = gWideDimension + 1;
   // Remember the initial position of the first match
   gWideBoardStart = gWideBoard + widePlus1;
   // and the match one in and one row down for quick search 
   // for totally surrounded matches
   gWideBoardStartJumpToFirstMatch = gWideBoardStart + widePlus1;
   
   // copy the input board to my wide board
   register char* source = (char*)(board);
   register char* dest = gWideBoard + gWideDimension; 
      // start one row down as first row is already clear with NewPtrClear
   for (register long i = gDimension; i; --i)
      {
      // add an empty space at the start of the row. no need to 
      // set to zero as was done by NewPtrClear
      ++dest;
      for (register long col = gDimension; col; --col)
         {
         if (*source)
            {
            ++gNumMatches;
            *dest = 1;
            }
         // otherwise no need to set to zero as was set with NewPtrClear
         ++dest;
         ++source;
         }
      // add an empty space at the end of the row. no need to set to 
      // zero as was done by NewPtrClear
      ++dest;
      }
   // last row is already clear with NewPtrClear
   gCriticalCount = gNumMatches;
   gCriticalCount -= (gNumMatches >> 2); // take off a quarter of the matches
   }
OpponentMove
/*==========================================================*/
/* true if opponent played along a row, false if along a column */
/* number of the (origin zero) row (playingRow==true) or column (playingRow==false) that the 
opponent played */

/* if playingRow == true, the opponent played from (row,col) == 
      (rowOrColumnNumber,startingColOrRow) to (row,col) == 
      (rowOrColumnNumber,endingColOrRow) */
/* if playingRow == false, the opponent played from (row,col) == 
      (startingColOrRow,rowOrColumnNumber) to (row,col) == 
      (endingColOrRow,rowOrColumnNumber) */
/* board after your opponent's move */
/*==========================================================*/
void OpponentMove (bool playingRow, short rowOrColumnNumber, 
      short startingColOrRow, short endingColOrRow, 
      const char* /*board*/)
   {
   if (gGameOver) // don't care about my wide board anymore as I am no longer using it
      return;
   if (startingColOrRow > endingColOrRow) // taking backwards so swap them
      {
      short swap = startingColOrRow;
      startingColOrRow = endingColOrRow;
      endingColOrRow = swap;
      }
   DoWideMoveAndCountIslands (playingRow, rowOrColumnNumber, 
         startingColOrRow, endingColOrRow);
   }
YourMove
/*=======================================================*/
/* true if you played along a row, false if along a column */
/* number of the (origin zero) row (playingRow==true) or column 
      (playingRow==false) that you played */
/* if *playingRow == true, you played from (row,col) == 
      (*rowOrColumnNumber,*startingColOrRow) to (row,col) == 
      (*rowOrColumnNumber,*endingColOrRow) */
/* if *playingRow == false, you played from (row,col) == 
      (*startingColOrRow,*rowOrColumnNumber) to (row,col) == 
      (*endingColOrRow,*rowOrColumnNumber) */
/* return value is a pointer to a board after your move */
/*========================================================*/
const char* YourMove (bool* outPlayingRow, 
      short* outRowOrColumnNumber, short* outStartingColOrRow, 
      short* outEndingColOrRow)
   {
   bool playingRow;
   short rowOrColumnNumber;
   short startingColOrRow;
   short endingColOrRow;
   
   if (gGameOver) 
         // I may have actually lost, but I may also have won. Fast exit
      {
      TakeFirstMatch (rowOrColumnNumber, startingColOrRow);
      DoMove (true, rowOrColumnNumber, startingColOrRow, 
         startingColOrRow); 
         // apply my move to the main board
      *outPlayingRow = true;
      *outRowOrColumnNumber = rowOrColumnNumber;
      *outStartingColOrRow = startingColOrRow;
      *outEndingColOrRow = startingColOrRow;
      return gBoard;
      }
   
   if (gNumMatches > gCriticalCount)
      {
      QuickTakeSurroundedMatch (rowOrColumnNumber, 
            startingColOrRow);
      DoMove (true, rowOrColumnNumber, startingColOrRow, 
            startingColOrRow); 
         // apply my move to the main board
      DoWideMoveAndCountIslands (true, rowOrColumnNumber, 
            startingColOrRow, startingColOrRow); 
         // apply my move to my wide board and count new islands
      *outPlayingRow = true;
      *outRowOrColumnNumber = rowOrColumnNumber;
      *outStartingColOrRow = startingColOrRow;
      *outEndingColOrRow = startingColOrRow;
      return gBoard;
      }
   
   if (gNumIslands == gNumMatches) 
      // I've won or lost, I have no choice, so just take the first island
      {
      gGameOver = true; 
         // so will no longer get down this low in the routine as the game 
         // is basically finished
      TakeFirstMatch (rowOrColumnNumber, startingColOrRow);
      DoMove (true, rowOrColumnNumber, startingColOrRow, 
                  startingColOrRow); 
         // apply my move to the main board
         // no need to do anything more to my wide board as the game is over. 
         // Always take the first match in the main board
      *outPlayingRow = true;
      *outRowOrColumnNumber = rowOrColumnNumber;
      *outStartingColOrRow = startingColOrRow;
      *outEndingColOrRow = startingColOrRow;
      return gBoard;
      }
   
   gDoWideMove = true;
   bool winning;
   if (gLastMatchLoses)
      winning = (gNumIslands & 0x00000001) == 0; 
         // if there are an even number of islands I am currently winning
   else
      winning = (gNumIslands & 0x00000001) != 0; 
         // if there are an odd number of islands I am currently winning
   if (winning)
      {
      if (TakeAnIsland (rowOrColumnNumber, startingColOrRow)) 
         // taking an island will put me back into a winning position
         {
         playingRow = true;
         endingColOrRow = startingColOrRow;
         }
      else 
         // if I create another island I will be in a winning 
         // position as if I had taken an island
      if (!Create1Or3Islands (playingRow, rowOrColumnNumber, 
               startingColOrRow, endingColOrRow)) // could not create another island
         {
         TakeBestMatch (rowOrColumnNumber, startingColOrRow); 
            // still losing, just take one
         playingRow = true;
         endingColOrRow = startingColOrRow;
         }
      }
   else 
      // if I'm losing, keeping the same number of islands to KEEP it odd (or even) will 
      // then put me in a winning position at the END of my go
   if (!Create2Or4Islands (playingRow, rowOrColumnNumber, 
               startingColOrRow, endingColOrRow))
      { 
         // else taking a whole row or column might not create any more islands
      if (!TakeARowOrColumn (playingRow, rowOrColumnNumber, 
                  startingColOrRow, endingColOrRow)) 
            // only fails if taking any row still makes me lose
         {
         TakeBestMatch (rowOrColumnNumber, startingColOrRow);
         playingRow = true;
         endingColOrRow = startingColOrRow;
         }
      }
   // apply the move to my wide board
   if (gDoWideMove)
      DoWideMoveAndCountIslands (playingRow, rowOrColumnNumber, 
               startingColOrRow, endingColOrRow); 
      // apply my move to my wide board and count new islands
   else 
         // only need to look for new islands as I have already 
         // applied the move to the wide board during my analysis
      {
      if (gSetIslandSaver) // only left unset if gDoWideMove and did not undo
         {
         gSetIslandSaver = false;
         long count = endingColOrRow - startingColOrRow + 1; 
            // this is how many matches I took
         char* p = gIslandSaver; // clear any islands that I saved for undo
         while (count)
            {
            *p = 0;
            ++p;
            --count;
            }
         }
      CountIslands (playingRow, rowOrColumnNumber, startingColOrRow, 
               endingColOrRow); 
   // just count the number of new islands
      }
   // apply the move to the main board
   DoMove (playingRow, rowOrColumnNumber, startingColOrRow, 
               endingColOrRow); 
   
   // return the values of my move
   *outPlayingRow = playingRow;
   *outRowOrColumnNumber = rowOrColumnNumber;
   *outStartingColOrRow = startingColOrRow;
   *outEndingColOrRow = endingColOrRow;
   
   return gBoard;
   }
TermMatchsticks
/*====================================================*/
/* Clean up */
/*====================================================*/
void TermMatchsticks ()
   {
   ::DisposePtr (gWideBoard);
   ::DisposePtr (gIslandSaver);
   }

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

TinkerTool 5.4 - 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
Tinderbox 6.3.1 - Store and organize you...
Tinderbox is a personal content management assistant. It stores your notes, ideas, and plans. It can help you organize and understand them. And Tinderbox helps you share ideas through Web journals... Read more
Parallels Desktop 10.2.2 - Run Windows a...
Parallels Desktop is simply the world's bestselling, top-rated, and most trusted solution for running Windows applications on your Mac. With Parallels Desktop for Mac, you can seamlessly run both... Read more
Adobe Premiere Pro CC 2015 9.0.1 - Digit...
Premiere Pro CC 2015 is available as part of Adobe Creative Cloud for as little as $19.99/month (or $9.99/month if you're a previous Premiere Pro customer). Premiere Pro CS6 is still available for... Read more
Adobe After Effects CC 2015 13.5.1 - Cre...
After Effects CC 2015 is available as part of Adobe Creative Cloud for as little as $19.99/month (or $9.99/month if you're a previous After Effects customer). After Effects CS6 is still available... Read more
Adobe Creative Cloud 2.2.0.129 - Access...
Adobe Creative Cloud costs $49.99/month (or less if you're a previous Creative Suite customer). Creative Suite 6 is still available for purchase (without a monthly plan) if you prefer. Introducing... Read more
Tower 2.2.3 - Version control with Git m...
Tower is a powerful Git client for OS X that makes using Git easy and more efficient. Users benefit from its elegant and comprehensive interface and a feature set that lets them enjoy the full power... Read more
Apple Java 2015-001 - For OS X 10.7, 10....
Apple Java for OS X 2015-001 installs the legacy Java 6 runtime for OS X 10.11 El Capitan, OS X 10.10 Yosemite, OS X 10.9 Mavericks, OS X 10.8 Mountain Lion, and OS X 10.7 Lion. This package is... Read more
Adobe Muse CC 2015 2015.0.1 - Design and...
Muse CC 2015 is available as part of Adobe Creative Cloud for as little as $14.99/month (or $9.99/month if you're a previous Muse customer). Muse CS6 is still available for purchase (without a... Read more
Adobe Illustrator CC 2015 19.1.0 - Profe...
Illustrator CC 2015 is available as part of Adobe Creative Cloud for as little as $19.99/month (or $9.99/month if you're a previous Illustrator customer). Illustrator CS6 is still available for... Read more

Tap Delay (Music)
Tap Delay 1.0.0 Device: iOS Universal Category: Music Price: $4.99, Version: 1.0.0 (iTunes) Description: Back in the “old days”, producers and engineers created delay and echo effects using tape machines. Tap Delay combines the warm... | Read more »
This Week at 148Apps: July 20-24, 2015
July is Heating Up With 148Apps How do you know what apps are worth your time and money? Just look to the review team at 148Apps. We sort through the chaos and find the apps you're looking for. The ones we love become Editor’s Choice, standing out... | Read more »
Red Game Without A Great Name (Games)
Red Game Without A Great Name 1.0.3 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0.3 (iTunes) Description: The mechanical bird is flying through an unfriendly, Steampunk world. Help it avoid obstacles and deadly... | Read more »
Warhammer: Arcane Magic (Games)
Warhammer: Arcane Magic 1.0.2 Device: iOS Universal Category: Games Price: $9.99, Version: 1.0.2 (iTunes) Description: Engage in epic battles and tactical gameplay that challenge both novice and veteran in Warhammer: Arcane Magic, a... | Read more »
Mazes of Karradash (Games)
Mazes of Karradash 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: The city of Karradash is under attack: the monsters of the Shadow Realms are emerging from the depths.No adventurer is... | Read more »
Battle Golf is the Newest Game from the...
Wrassling was a pretty weird - and equally great - little wressling game. Now the developers, Folmer Kelly and Colin Lane, have turned their attention to a different sport: golfing. This is gonna be weird. [Read more] | Read more »
Qbert Rebooted has the App Store Going...
The weird little orange... whatever... is back, mostly thanks to that movie which shall remain nameless (you know the one). But anyway it's been "rebooted" and now you can play the fancy-looking Qbert Rebooted on iOS devices. [Read more] | Read more »
Giant Monsters Run Amok in The Sandbox...
So The Sandbox has just hit version number 1.99987 (seriously), and it's added a lot more stuff. Just like every other update, really. [Read more] | Read more »
Fish Pond Park (Games)
Fish Pond Park 1.0.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0.0 (iTunes) Description: Nurture an idyllic slice of tourist's heaven into the top nature spot of the nation, furnishing it with a variety of... | Read more »
Look after Baby Buddy on your Apple Watc...
Parigami Gold is the new premium version of the match three puzzler that includes Apple Watch support and all new content. You won't simply be sliding tiles around on your wrist, the Apple Watch companion app is an all new mini-game in itself. You'... | Read more »

Price Scanner via MacPrices.net

2015 13-inch 2.7GHz Retina MacBook Pro on sal...
B&H Photo has the new 2015 13″ 2.7GHz/128GB Retina MacBook Pro on sale today for $1199 including free shipping plus NY sales tax only. Their price is $100 off MSRP. Read more
2.8GHz Mac mini available for $988, includes...
Adorama has the 2.8GHz Mac mini available for $988, $11 off MSRP, including a free copy of Apple’s 3-Year AppleCare Protection Plan. Shipping is free, and Adorama charges sales tax in NY & NJ... Read more
Updated Mac Price Trackers
We’ve updated our Mac Price Trackers with the latest information on prices, bundles, and availability on systems from Apple’s authorized internet/catalog resellers: - 15″ MacBook Pros - 13″ MacBook... Read more
High-Precision Battery Fuel Gauge IC Extends...
Renesas Electronics Corporation has announced its new lithium-ion (Li-ion) battery fuel gauge IC, the RAJ240500, designed to extend battery life for connected mobile devices such as tablets, notebook... Read more
27-inch 3.3GHz 5K iMac on sale for $1799, $20...
B&H Photo has the 27″ 3.3GHz 5K iMac on sale for $1799 including free shipping plus NY tax only. Their price is $200 off MSRP, and it’s the lowest price available for this model from any Apple... Read more
Twelve South Free Dual Screen Backgrounds Co...
Twelve South has posted a second collection of travel Desktop photos, noting: For the Twelve South team, a vacation is never just a vacation. It’s a time to try out new prototypes on the road, visit... Read more
Apple Refurbished iMacs available for up to $...
The Apple Store has Apple Certified Refurbished iMacs available for up to $380 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free: - 27″ 3.5GHz 5K iMac – $1949 $... Read more
Tablets: Why Microsoft’s Surface Is Soaring W...
In contrast to Apple’s record fiscal third quarter reported this week, Microsoft had a miserable latest quarter with its revenues falling by 5.1 percent, hammered by ongoing weak PC demand, and... Read more
Sale! 13″ 1.6GHz/256GB MacBook Air for $1099,...
B&H Photo has the 13″ 1.6GHz/256GB MacBook Air on sale for $1099 including free shipping plus NY tax only. Their price is $100 off MSRP, and it’s the lowest price available for this model. Read more
iPad mini 4 To Be Upgraded To iPad Air 2 Spec...
There’s a certain inevitability about making Apple product predictions this time of year. Come September, we can pretty reliably count on the release of refreshed iPhones, along with the iOS 9... Read more

Jobs Board

*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales. Specialist - Retail Customer Service and Sales. Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Customer Experience (ACE) Leader - A...
…management to deliver on business objectives Training partner store staff on Apple products, services, and merchandising guidelines Coaching partner store staff on Read more
Project Manager - *Apple* Pay Security - Ap...
**Job Summary** The Apple Pay Security team is seeking a highly organized, results-driven Project Manager to drive the development of Apple Pay Security. If you are Read more
*Apple* TV Product Design Internship (Spring...
…the mechanical design effort associated with creating world-class products with the Apple TV PD Group. Responsibilities will include working closely with manufacturing, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.