Having read the SolitaireGame article by StephenTaylor I thought I would set out a series of functions which use the creation of playing cards to demonstrate APL's ability to use data stored in third party resources, native files and the workspace and to manipulate the data using boolean patterns. APL is particularly good at exploiting data patterns.
The functions are written in APL+WIN but they should be readily convertible into any GUI enabled interpreter as they only use a simple form and a picture control for the graphics and ⎕wcall for making low level windows calls.
The main function Solitaire creates a graphical card table complete with the initial deal. See Stephen's article for the logic underpinning the SolitaireGame itself. The functions that follow implement the data handling involved and can be used to create any card game. More importantly they demonstrate some of APL's data handling capabilities. I have deliberately written the functions in what I call "long hand APL" so that they are easy to read and understand.
∇ Solitaire;i;j;cbk;n;bkg;ep;c ⍝Display Card Table CardTable 'fm' ⎕wi 'caption' 'Solitaire' ⍝Get the card back bitmap cbk←MakeCardDLL 62 ⍝Get the empty pile bitmap ep←MakeCardDLL 53 ⍝Select 10 cards at random c←10?52 ⍝Display discard piles :for i :in ⍳4 bkg←(∼∆bkg)×⎕wi 'Get' 7 (180+86×i) 96 71 ⎕wi 'Put' (bkg+∆bkg×(1↑,bkg)×∼ep=0) 7 (180+86×i) :endfor ⍝Display draw pile :for i :in ⍳3 bkg←(∼∆bkg)×⎕wi 'Get' (5+2×i) (5+2×i) 96 71 ⎕wi 'Put' (bkg+cbk) (5+2×i) (5+2×i) :endfor ⍝Display first three cards drawn :for i :in ⍳3 bkg←(∼∆bkg)×⎕wi 'Get' (7+2×i) (80+15×i) 96 71 ⎕wi 'Put' (bkg+MakeCardDLL c[i]) (7+2×i) (80+15×i) :endfor ⍝Display the seven initial deal piles :for i :in ⍳7 ⍝Face down cards :for j :in ⍳i-1 bkg←(∼∆bkg)×⎕wi 'Get'(130+3×j) (7+86×i-1) 96 71 ⎕wi 'Put' (bkg+cbk) (130+3×j) (7+86×i-1) :endfor ⍝Face up cards bkg←(∼∆bkg)×⎕wi 'Get' (130+3×i) (7+86×i-1) 96 71 ⎕wi 'Put' (bkg+MakeCardDLL c[i+3]) (130+3×i) (7+86×i-1) :endfor 'fm' ⎕wi 'visible' 1 ∇
This is what you get:
CardTable creates the form and picture control onto which the cards are drawn.
∇ CardTable ⍝Create a form to hold the picture control ⎕wself←'fm' ⎕wi 'Create' 'Form' ⎕wi 'scale' 5 ⎕wi 'color' (40 157 47) ¯1 ⎕wi 'style' 16 ⎕wi 'size' 400 600 ⎕wi 'caption' 'Card Table' ⎕wi 'visible' 0 ⍝Create a picture control to hold the card table ⎕wself←'fm.pic' ⎕wi 'New' 'Picture' ⎕wi 'scale' 5 ⎕wi 'where' 0 0 400 600 ⎕wi 'imagesize' 400 600 ⎕wi 'border' 0 ∇
MakeCardDLL extracts the playing card bitmaps from the MS cards.dll which is used by MS for their card games. The cards are numbered resources within the dll with the basic pack being numbered 1 to 52 and the placeholder card 53, card backs 54 to 65 and the red cross and green circle 67 and 68. In windows XP home the cards.dll is located in the \windows\system32\ directory.
The bitmaps are modified to make sure they all have black boarders, white backgrounds and rounded corners.
∇ r←MakeCardDLL n;hres;hbmp ⍝Load the windows card bitmap dll and get its handle hres←⎕wcall 'LoadLibrary' 'c:\windows\system32\cards.dll' ⍝Get handle to the required bitmap via its resource number hbmp←⎕wcall 'LoadBitmap' hres n ⍝Get the bitmap bits r←⎕wcall 'GetBitmapBits' hbmp 27264 (27264⍴⎕tcnul) ⍝If get operation unsuccessful release the handles and exit :if 0=↑r :goto rel ⍝Make the background and border bit masks ∆bkg and ∆bdr MakeMasks ⍝If the number of bytes = 960 its a black non-picture card ⍝These are stored in the dll as momochrome bitmaps so unpack bits :elseif 960=↑r r←96 71↑11 ⎕dr 96 10⍴,∊r ⍝Add white background r←r×∆bkg×16777215 ⍝All other cards stored as 24 bit colour bitmaps ⍝Convert the colour data to single integer colour values :else r←0 ¯1↓6816 4⍴¯1+⎕av⍳,∊r r←96 71⍴256 256 256⊥⍉r :endif ⍝Convert all borders to black r←r×∆bdr×∆bkg ⍝Release resource handles back to the system rel: 0 0⍴⎕wcall 'FreeLibrary' hres 0 0⍴⎕wcall 'DeleteObject' hbmp ∇
MakeMasks is used to create the masks used to round the corners of the cards. These masks are stored in global variables for later use.
∇ MakeMasks ⍝Create a background bit mask with shaped corners ∆bkg←96 71⍴1 ∆bkg[1 96;1 2 70 71]←0 ∆bkg[2 95;1 71]←0 ⍝Create a border bit mask with shaped corners ∆bdr←96 71⍴1 ∆bdr[1 96;2+⍳67]←0 ∆bdr[2 95;2 70]←0 ∆bdr[2+⍳92;1 71]←0 ∇
Coding the above functions is all you need to use the third party cards.dll if card playing is your primary interest. The functions that follow are more focused on data handling and are restricted to just the basic 52 card pack.
FileCards extracts the bitmaps from the dll, creates a native text file and uses the GetBits function to convert the bitmap data into a suitable form for efficient storage in a native text file and its subsequent retrieval and processing. The file created by running FileCards is attached: cards.bytes. The extension of the attached file has been changed to prevent it displaying its contents on the download page. Anyone wishing to use it with these functions rather than generating it using FileCards should rename it to cards.txt after downloading.
∇ FileCards;i ⍝Make the background and border bit masks ∆bkg and ∆bdr MakeMasks ⍝Create the cards file 'c:\cards.txt' ⎕ncreate ¯1 ⍝Get the monchrome masks for the pack :for i :in ⍳52 (,GetBits i) ⎕nappend ¯1 :endfor ⍝Add blank and border masks ((,∆bkg),,∆bdr) ⎕nappend ¯1 ⍝Untie cards file ⎕nuntie ¯1 ∇
GetBits extracts the bitmap bit data for each card and converts it into a boolean bit mask or masks. The black suit cards 1 to 10 are monochrome bitmaps whilst the rest are 24 bit colour bitmaps and need to be processed accordingly. The cards 1 to 10 in all suits can be represented by one mask whilst the picture cards require three masks to represent their constituent colours black, yellow and red. Ravelling the bit masks and storing them in a text file effectively packs the bits into bytes and they show up as characters in the text file.
With this byte structure we have effectively compressed and mildly encrypted the data needed to represent a pack of cards. These techniques can be used with all forms of data that can be converted to integers by suitable scaling before encoding and they are the basis of a lot of commercial encoding and compression techniques. Take a look at the AplToUnicode article to see how it is done for the integers representing the unicode utf-8 code points.
∇ r←GetBits n;hres;hbmp ⍝Load the windows card bitmap dll and get its handle hres←⎕wcall 'LoadLibrary' 'c:\windows\system32\cards.dll' ⍝Get handle to the required bitmap via its resource number hbmp←⎕wcall 'LoadBitmap' hres n ⍝Get the bitmap bits r←⎕wcall 'GetBitmapBits' hbmp 27264 (27264⍴⎕tcnul) ⍝If get operation unsuccessful release the handles and exit :if 0=↑r :goto rel ⍝If the number of bytes = 960 its a monochrome bitmap so unpack and reverse black and white :elseif 960=↑r r←∼96 71↑11 ⎕dr 96 10⍴,∊r ⍝Convert the three colour data to single integer colour value :else r←0 ¯1↓6816 4⍴¯1+⎕av⍳,∊r r←96 71⍴256 256 256⊥⍉r ⍝If it is a picture card split the black, red and yellow data into reverse monochrome masks :if ×+/(n≤¨13×⍳4)^n≥¨¯2+13×⍳4 r←(r=0)⍪(r=255)⍪r=65535 ⍝If it is a red suit non picture card convert to a reverse monochrome mask :else r←r=255 :endif :endif ⍝Remove the border from the monochrome masks r[1 2 95 96;]←r[1 2 95 96;]×0 r[;1 71]←r[;1 71]×0 ⍝Release resource handles back to the system rel: 0 0⍴⎕wcall 'FreeLibrary' hres 0 0⍴⎕wcall 'DeleteObject' hbmp ∇
Knowing the byte structure in which we stored the cards in the file we can now use MakeCardF to effectively index into the text file to read the bytes we want. The bytes are then unpacked to recover the boolean masks which are then used to recreate cards ready for drawing. I find the use of native text files in this way to be an extremely quick and efficient means of handling certain types of data.
∇ r←MakeCardF n;v;mb;my;mr ⍝Identify position of card mask(s) in the pack r←¯1↓0,+\v←(52⍴(10⍴1),3⍴3),1 1 ⍝Tie the cards file 'c:\cards.txt' ⎕ntie ¯1 ⍝Make the background and border bit masks ∆bkg and ∆bdr MakeMasks ⍝If the card is picture card split out the three colour masks :if v[n]=3 mb←96 71⍴11 ⎕dr ⎕nread ¯1 82 852,(852×r[n]) mr←96 71⍴11 ⎕dr ⎕nread ¯1 82 852,(852×1+r[n]) my←96 71⍴11 ⎕dr ⎕nread ¯1 82 852,(852×2+r[n]) ⍝Build the card on a white background with a black border r←∆bdr×(16777215×∆bkg-mb+mr+my)+(255×mr)+65535×my :else mb←96 71⍴11 ⎕dr ⎕nread ¯1 82 852,(852×r[n]) r←∆bdr×(16777215×∆bkg-mb)+mb×0 255 255 255 0 0[⌈n÷13] :endif ⍝Untie the cards file ⎕nuntie ¯1 ∇
LoadCards retrieves the bit masks for all 52 cards from the native file and stores them in a workspace variable ∆pack. This variable can be stored in the workspace for use in any card game functions written.
∇ LoadCards;i ⍝Tie the cards file 'c:\cards.txt' ⎕ntie ¯1 ⍝Get the monchrome masks for the pack ∆pack←⎕nread ¯1 82,(⎕nsize ¯1),0 ⍝Extract the card background and border masks ∆bkg←96 71⍴11 ⎕dr 852↑¯1704↑∆pack ∆bdr←96 71⍴11 ⎕dr ¯852↑∆pack ∆pack←¯1704↓∆pack ⍝Untie cards file ⎕nuntie ¯1 ∇
MakeCardWS uses the bit masks stored in the workspace variable ∆pack to generate the cards ready for drawing.
∇ r←MakeCardWS n;v;bkg;bdr;mb;my;mr ⍝Identify position of card mask(s) in the pack r←¯1↓0,+\v←(52⍴(10⍴1),3⍴3),1 1 r←(852×r[n])+⍳852×v[n] ⍝Make the background and border bit masks ∆bkg and ∆bdr MakeMasks ⍝If the card is picture card split out the three colour masks :if v[n]=3 mb←96 71⍴11 ⎕dr 852↑∆pack[r] mr←96 71⍴11 ⎕dr 852↑852↓∆pack[r] my←96 71⍴11 ⎕dr ¯852↑∆pack[r] ⍝Build the card on a white background with a black border r←∆bdr×(16777215×∆bkg-mb+mr+my)+(255×mr)+65535×my :else mb←96 71⍴11 ⎕dr ∆pack[r] r←∆bdr×(16777215×∆bkg-mb)+mb×0 255 255 255 0 0[⌈n÷13] :endif ∇
ShowCard takes a left argument of 'DLL', 'F' or 'WS' to represent the source of the bit mask data and a right argument of the card number from 1 to 52 for the F and WS cases and all the numbers listed previously for the DLL case. With these it displays the selected card on the card table.
∇ src ShowCard n;c ⍝Requires card table to be active CardTable ⍝Make the background and border bit masks ∆bkg and ∆bdr MakeMasks ⍝Obtain background colours to construct shaped corners c←(∼∆bkg)×⎕wi 'Get' 0 0 96 71 ⍝Set up source list :select src ⍝Show the card on the table :caselist 'DLL' 'dll' ⎕wi 'Put' (c+MakeCardDLL n) 0 0 :caselist 'F' 'f' ⎕wi 'Put' (c+MakeCardF n) 0 0 :caselist 'WS' 'ws' ⎕wi 'Put' (c+MakeCardWS n) 0 0 :endselect 'fm' ⎕wi 'visible' 1 ∇
I hope this article as been a useful introduction to some of the ways data can be accessed and stored with APL. My preferences are to use third party resources where appropriate ones exist, followed by native text files for large volumes of data and the workspace variables for small volumes of data.
I also hope that anyone new to APL will recognise the power of thinking in patterns and particularly boolean patterns when approaching certain data processing problems using APL. All three of the articles I have written so far for the wiki, this one, AplToUnicode and the TheThreeGoatsII problem use this pattern approach as does KaiJaeger's Strip function which he uses to strip the html tags out of the text on a web page in his AplIn20Minutes article.