Drawing old playing card images for bridge


About 11 years ago I wrote a blog about Contract bridge card distributions. When you play bridge, with a normal 52 card deck, it helps to know about how often a particular suit distribution might occur. For example, if you and your partner have 9 Spades, then the opponent’s spade holding of 4 spades might be divided 2 each.

This was around the time of Windows XP, which came with a Solitaire program. I think every new version of Windows had a game, like MineSweeper, Hearts, Solitaire.

Solitaire used a Dll in the Windows\System32 folder called Cards.dll. Because many of you may not have access to a Windows XP machine, I’ve made it available here.

I can still run that Visual FoxPro card distribution calculation program on Windows 10 with Visual FoxPro 9 on my brand new Surface Pro 4, and it runs really fast.

However, because the screen resolution is so much higher on newer machines, the cards look so small. Of course, I could change the Dots Per Inch in display settings to change the card size, but I could also write a program.

Cards.Dll contains the actual bitmaps for playing cards as resources. It also includes some simple functions to draw the cards. The FoxPro program calls those functions to draw those cards very quickly, dealing dozens of hands per second. The sample below draws the cards with C++, using both the provided Card.Dll drawing functions, and using GDIPlus.

I knew that the Card DLL functions could scale, but only by integer amounts, whereas GDIPlus can scale with floats, like 2.5.

The sample C++ code below has 3 parts:

1. Load and Initialize the Cards.Dll

2. Call the initialization

3. Handle the WM_PAINT message, which merely draws all the cards on the window, alternating drawing methods to help show the difference.

Start Visual Studio 2015 (most prior versions will work similarly)

File->New->Project->Visual C++->Win32->Win32 Project. Call it “Cards” (this is important, so it matches the code below)

Paste in the code below to replace the entire contents of Cards.Cpp

<code>

// CARDS.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include <ObjIdl.h>
#include <GdiPlus.h>
#include <GdiPlusFlat.h>
#pragma comment(lib,"GdiPlus.lib")
#include "Cards.h"

using namespace Gdiplus;

typedef int (WINAPI * cdtInit)(
    DWORD *xpix,
    DWORD *ypix
    );

typedef int (WINAPI * cdtTerm)(
    );
typedef int (WINAPI * cdtDraw)(
    HDC hdc,
    DWORD x,
    DWORD y,
    DWORD nCard,
    DWORD draw,
    DWORD clr
    );
typedef int (WINAPI * cdtDrawExt)(
    HDC hdc,
    DWORD x,
    DWORD y,
    DWORD dx,
    DWORD dy,
    DWORD nCard,
    DWORD draw,
    DWORD clr
    );

cdtInit g_pfncdtInit;
cdtDraw g_pfncdtDraw;
cdtDrawExt g_pfncdtDrawExt;
DWORD g_xpix; //  96
DWORD g_ypix; // 71
ULONG_PTR gdiplustoken;
HINSTANCE g_hModuleCards;

void InitCardsDll()
{
    g_hModuleCards = LoadLibrary(L"cards.dll");
    if (g_hModuleCards == 0)
    {
        MessageBox(0, L"Couldn't find Cards.dll", L"Cards", 0);
        exit(0);
    }
    WCHAR strText[MAX_PATH];
    GetModuleFileName(g_hModuleCards, strText, _countof(strText));
    g_pfncdtInit = (cdtInit)GetProcAddress(g_hModuleCards, "cdtInit");
    g_pfncdtDraw = (cdtDraw)GetProcAddress(g_hModuleCards, "cdtDraw");
    g_pfncdtDrawExt = (cdtDrawExt)GetProcAddress(g_hModuleCards, "cdtDrawExt");

    (*g_pfncdtInit)(&g_xpix, &g_ypix); // returns 1 on success.
    GdiplusStartupInput gdipStartupInput;
    GdiplusStartup(&gdiplustoken, &gdipStartupInput, NULL);
}



#define MAX_LOADSTRING 100

// Global Variables:
HINSTANCE hInst;                                // current instance
WCHAR szTitle[MAX_LOADSTRING];                  // The title bar text
WCHAR szWindowClass[MAX_LOADSTRING];            // the main window class name

// Forward declarations of functions included in this code module:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);


int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: Place code here.
    InitCardsDll();

    // Initialize global strings
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_CARDS, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // Perform application initialization:
    if (!InitInstance(hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_CARDS));

    MSG msg;

    // Main message loop:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    GdiplusShutdown(gdiplustoken);

    return (int)msg.wParam;
}



//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_CARDS));
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_CARDS);
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    hInst = hInstance; // Store instance handle in our global variable

    HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

    if (!hWnd)
    {
        return FALSE;
    }

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    return TRUE;
}

//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE:  Processes messages for the main window.
//
//  WM_COMMAND  - process the application menu
//  WM_PAINT    - Paint the main window
//  WM_DESTROY  - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
    {
        int wmId = LOWORD(wParam);
        // Parse the menu selections:
        switch (wmId)
        {
        case IDM_ABOUT:
            DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
            break;
        case IDM_EXIT:
            DestroyWindow(hWnd);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
    }
    break;
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        float scale = 2.5;
        for (int denom = 0; denom < 13; denom++)
        {
            for (int suit = 0; suit < 4; suit++)
            {
                int xpos = (8 + g_xpix) * denom;
                int ypos = (8 + g_ypix) * suit;
                // alternate drawing methods
                if (denom % 2 != 0)
                {
                    int crd = 4 * denom + suit;
                    // use the cdtDrawExt function in the Cards.DLL
                    g_pfncdtDrawExt(
                        hdc,
                        xpos,
                        ypos,
                        (int)(g_xpix*scale),
                        (int)(g_ypix*scale),
                        crd,
                        0,
                        0);
                }
                else
                {
                    int crd = suit * 13 + denom;
                    // use GDIPlus to create a Bitmap from a resource
                    Bitmap *pBitmap = new Bitmap(
                        g_hModuleCards,
                        MAKEINTRESOURCE(crd + 1));
                    auto x = pBitmap->GetWidth() * scale;
                    auto y = pBitmap->GetHeight() * scale;
                    // scale the bitmap
                    Bitmap bmpScaled((int)x, (int)y);
                    Graphics gtemp(&bmpScaled);
                    gtemp.ScaleTransform(scale, scale);
                    gtemp.DrawImage(pBitmap, 0, 0);
                    Graphics hdcGraphics(hdc);
                    hdcGraphics.DrawImage(&bmpScaled, xpos, ypos);
                }
            }
        }
        EndPaint(hWnd, &ps);
    }
    break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

</code>

Comments (1)

  1. Rui Nogueira says:

    Hi,

    This post is very good, but even if I have tried, I never managed to learn the rules and method to play the cars game: Bridge.

    Thank You,

    http://www.arcns.no.sapo.pt

Skip to main content