COMPUTER.CPP

// Filename: Computer.cpp 
//
// Description: Implementation of CComputer
// This file contains the code that handles the interaction between a human
// player and a computer player.
//
// This file is provided as part of the Microsoft Transaction Server Samples
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved

#include "stdafx.h"
#include "tServer.h"
#include "Computer.h"

#include <mtx.h>
#include <mtxspm.h>
#include <time.h>

/////////////////////////////////////////////////////////////////////////////
//

STDMETHODIMP CComputer::InterfaceSupportsErrorInfo(REFIID riid)
{
static const IID* arr[] =
{
&IID_IComputer,
};

for (int i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
{
if (InlineIsEqualGUID(*arr[i],riid))
return S_OK;
}
return S_FALSE;
}


STDMETHODIMP CComputer::AddNewGame (IN VARIANT_BOOL bEasy, OUT VARIANT* pvGameID, OUT VARIANT* pvOrder,
OUT VARIANT* pvX, OUT VARIANT* pvY) {

HRESULT hr = S_OK;

IObjectContext* pObjectContext = NULL;

ISharedPropertyGroupManager* spmMgr = NULL;
ISharedPropertyGroup* spmGroup = NULL;

ISharedProperty* spmPropCounter = NULL;
ISharedProperty* spmPropState = NULL;

pvGameID->vt = VT_I4;
pvOrder->vt = VT_I4;
pvX->vt = VT_I4;
pvY->vt = VT_I4;

long lGameID = 0;
long lOrder = 0;
long lNewX = 0;
long lNewY = 0;


try {

// Get the object context
THROW_ERR ( hr = GetObjectContext(&pObjectContext) );
if (pObjectContext == NULL) {
THROW_ERR ( E_FAIL );
}

// Create the SharedPropertyGroupManager
THROW_ERR ( pObjectContext->CreateInstance (CLSID_SharedPropertyGroupManager, IID_ISharedPropertyGroupManager, (void**)&spmMgr) );

// Create the SharedPropertyGroup
LONG lIsolationMode = LockMethod;
LONG lReleaseMode = Process;
VARIANT_BOOL bExists = VARIANT_FALSE;
THROW_ERR ( spmMgr->CreatePropertyGroup (L"TicTacToe", &lIsolationMode, &lReleaseMode, &bExists, &spmGroup) );

// Create the counter properties
THROW_ERR ( spmGroup->CreateProperty (L"Counter", &bExists, &spmPropCounter) );

// Obtain the current value of the counter
CComVariant vCounter;
THROW_ERR ( spmPropCounter->get_Value (&vCounter) );

// Assign gameID
if (vCounter.lVal == 0)
vCounter.lVal = 1;

lGameID = vCounter.lVal;

// Update the counter
vCounter.lVal ++;
THROW_ERR ( spmPropCounter->put_Value (vCounter) );

// Set the state variable
TCHAR szBuf [512];
BSTR bstrState;

wsprintf (szBuf, _T("%dState"), lGameID);
bstrState = TCHAR2BSTR (szBuf);
THROW_ERR ( spmGroup->CreateProperty (bstrState, &bExists, &spmPropState) );

CComVariant vState;
vState.vt = VT_I4;
vState.lVal = 4;
THROW_ERR ( spmPropState->put_Value (vState) );

// Initialize the array
InitArray();
THROW_ERR ( SaveArray(lGameID) );

// Assign order randomly
srand ((unsigned) time (NULL));
lOrder = rand() % 2 + 1;

// Get computer's first move if player's order is 2
if (lOrder == 2) {
GetMove (lGameID, bEasy, lNewX, lNewY);
plField [lNewX][lNewY] = 2;
SaveArray(lGameID);
}

// Prepare return values
pvX->lVal = lNewX;
pvY->lVal = lNewY;
pvOrder->lVal = lOrder;
pvGameID->lVal = lGameID;

// We're finished and happy
pObjectContext->SetComplete();

} catch (HRESULT hr) {

// Create an ErrorInfo object
ICreateErrorInfo* pCreateErrInfo = NULL;
IErrorInfo* pErrInfo = NULL;

CreateErrorInfo (&pCreateErrInfo);
pCreateErrInfo->QueryInterface (IID_IErrorInfo, (LPVOID FAR*) &pErrInfo);

// Fill in error information
TCHAR szErr [512];
wsprintf (szErr, _T("Error %d occurred in Computer::AddNewGame()"), hr);
BSTR bstrDesc = TCHAR2BSTR (szErr);
pCreateErrInfo->SetGUID (IID_IComputer);
pCreateErrInfo->SetSource (L"Computer");
pCreateErrInfo->SetDescription (bstrDesc);
::SysFreeString (bstrDesc);

// Confirm error information
SetErrorInfo (0, pErrInfo);

// Clean up the error objects
if (pCreateErrInfo)
pCreateErrInfo->Release();

if (pErrInfo)
pErrInfo->Release();

// Indicate our unhappiness
if (pObjectContext)
pObjectContext->SetAbort();

hr = E_FAIL;
}

if (pObjectContext)
pObjectContext->Release();

if (spmMgr)
spmMgr->Release();

if (spmGroup)
spmGroup->Release();

if (spmPropCounter)
spmPropCounter->Release();

if (spmPropState)
spmPropState->Release();

return hr;
}


STDMETHODIMP CComputer::NewMove (IN long lGameID, IN VARIANT_BOOL bEasy, IN long lX, IN long lY,
OUT VARIANT* pvMyX, OUT VARIANT* pvMyY, OUT VARIANT* pvWin) {
// Win protocol
// ============
// 0 -> moves did not end game
// 1 -> player won
// -1 -> computer won
// 2 -> player's move tied the game
// -2 -> computer's move tied the game

HRESULT hr = S_OK;

pvMyX->vt = VT_I4;
pvMyY->vt = VT_I4;
pvWin->vt = VT_I4;

long lTurns = 0;
long lWin = 0;
long lNewX = 0;
long lNewY = 0;

IObjectContext* pObjectContext = NULL;


try {

// Get the object context
THROW_ERR ( hr = GetObjectContext(&pObjectContext) );
if (pObjectContext == NULL) {
THROW_ERR ( E_FAIL );
}

THROW_ERR ( LoadArray(lGameID) );

// Enter new move
plField [lX][lY] = 1;

// Check for player's win
if (IsWin (1)) {

// Player won
lWin = 1;

} else {

// Check for fullness
lTurns = HowManyTurns();

if (lTurns == 9) {

// Tie
lWin = - 2;

} else {

// Get computer's move
GetMove (lGameID, bEasy, lNewX, lNewY);
plField[lNewX][lNewY] = 2;

// Check for computer win
if (IsWin(2))

lWin = - 1;

else

// Check for fullness
if (lTurns == 8)
lWin = 2;
}
}

// Save array
THROW_ERR ( SaveArray(lGameID) );

// Set return values
pvMyX->lVal = lNewX;
pvMyY->lVal = lNewY;
pvWin->lVal = lWin;

// We're finished and happy
pObjectContext->SetComplete();

} catch (HRESULT hr) {

// Create an ErrorInfo object
ICreateErrorInfo* pCreateErrInfo = NULL;
IErrorInfo* pErrInfo = NULL;

CreateErrorInfo (&pCreateErrInfo);
pCreateErrInfo->QueryInterface (IID_IErrorInfo, (LPVOID FAR*) &pErrInfo);

// Fill in error information
TCHAR szErr[512];
wsprintf (szErr, _T("Error %d occurred in Computer::NewMove()"), hr);
BSTR bstrDesc = TCHAR2BSTR (szErr);
pCreateErrInfo->SetGUID (IID_IComputer);
pCreateErrInfo->SetSource (L"Computer");
pCreateErrInfo->SetDescription (bstrDesc);
::SysFreeString (bstrDesc);

// Confirm error information
SetErrorInfo (0, pErrInfo);

// Clean up the error objects
if (pCreateErrInfo)
pCreateErrInfo->Release();

if (pErrInfo)
pErrInfo->Release();

// Indicate our unhappiness
if (pObjectContext)
pObjectContext->SetAbort();

hr = E_FAIL;
}

if (pObjectContext)
pObjectContext->Release();

return hr;
}


void CComputer::GetMove (long lGameID, VARIANT_BOOL bEasy, long& lX, long& lY) {

long lFlag = 0;
long lNewX = 0;
long lNewY = 0;

// Perhaps there's only possible move
if (HowManyTurns() == 8) {

// Scan for open tile
long i,j;
for (i = 0; i < 3; i ++) {
for (j = 0; j < 3; j ++) {
if (plField[i][j] == 0) {
lNewX = i;
lNewY = j;
lFlag = 1;
}
}
}
}

// Check for win
if (lFlag == 0)
lFlag = LineScan (2, lNewX, lNewY);

// Check for block
if (lFlag == 0)
lFlag = LineScan (1, lNewX, lNewY);

// Else create a new move
if (lFlag == 0) {

// Prepare random seed
srand ((unsigned) time (NULL));

// Decide intelligence level
if (!bEasy) {

// Place in center if it's open
if (plField[1][1] == 0) {
lNewX = 1;
lNewY = 1;
lFlag = 1;

} else {

// Place on corner, if opponent placed first move in center
long lTurns = HowManyTurns();
if (lTurns == 1 && plField[1][1] > 0) {
lNewX = (rand() % 2 == 0) ? 0:2;
lNewY = (rand() % 2 == 0) ? 0:2;
lFlag = 1;
} else {

// Place on opposite tile, if opponent's second
// move was placed on diagonal
if (lTurns == 2) {
if (plField[0][0] == 1) {
lNewX = 2;
lNewY = 2;
lFlag = 1;
}

if (plField[0][2] == 1) {
lNewX = 2;
lNewY = 0;
lFlag = 1;
}

if (plField[2][0] == 1) {
lNewX = 0;
lNewY = 2;
lFlag = 1;
}

if (plField[2][2] == 1) {
lNewX = 0;
lNewY = 0;
lFlag = 1;
}
}
}
}

// Else find most intelligent move
if (lFlag == 0) {

float value = 0;
float maxValue = 0;

for (long i = 0; i < 3; i ++) {
for (long j = 0; j < 3; j ++) {

// Evaluate tile
value = Evaluate (i, j);

// Randomize equal choices
if (value == maxValue && rand() % 2 == 0) {

lNewX = i;
lNewY = j;

} else {

if (value > maxValue) {
maxValue = value;
lNewX = i;
lNewY = j;
}
}
}
}
}

} else {

// Choose a random slot
while (lFlag == 0) {

lNewX = rand() % 3;
lNewY = rand() % 3;

if (plField [lNewX][lNewY] == 0)
lFlag = 1;
}
}
}

// Prepare return values
lX = lNewX;
lY = lNewY;
}

float CComputer::Evaluate (long lX, long lY) {

long lNewX, lNewY, lNewX2, lNewY2;
float fEval1, fEval2, fEval3;

if (plField[lX][lY] != 0) {

lX = - 100;
lY = - 100;
return - 100;
} else {

// Evaluate defensive options
plField[lX][lY] = 1;
fEval2 = (float) (LineScan (1, lNewX, lNewY) / 3);

// Evaluate offensive options
plField[lX][lY] = 2;
fEval1 = (float) LineScan (2, lNewX, lNewY);

// Evaluate opponent's follow-up
if (fEval1 > 0) {
plField[lNewX][lNewY] = 1;
fEval3 = (float) (LineScan (1, lNewX2, lNewY2) * 0.9);
plField[lNewX][lNewY] = 0;
} else
fEval3 = 0;

// Restore tiles and return final evaluation
plField[lX][lY] = 0;
return fEval1 + fEval2 - fEval3;
}
}


bool CComputer::IsWin (long lPlayer) {

bool bWin = false;

for (int i = 0; i < 3; i ++) {
if (plField [i][0] == plField [i][1] && plField [i][1] == plField [i][2] && plField [i][0] == lPlayer)
bWin = true;
if (plField [0][i] == plField [1][i] && plField [1][i] == plField [2][i] && plField [0][i] == lPlayer)
bWin = true;
}

if (plField [0][0] == plField [1][1] && plField [1][1] == plField [2][2] && plField [1][1] == lPlayer)
bWin = true;
if (plField [2][0] == plField [1][1] && plField [1][1] == plField [0][2] && plField [1][1] == lPlayer)
bWin = true;

return bWin;
}


long CComputer::HowManyTurns () {

long lTurns = 0;

for (int i = 0; i < 3; i ++) {
for (int j = 0; j < 3; j ++) {
if (plField[i][j] > 0) {
lTurns ++;
}
}
}

return lTurns;
}


long CComputer::LineScan (long lPlayer, long& lX, long& lY) {

long lCounter = 0;
lX = - 100;
lY = - 100;

// Horizontal lines
for (int i = 0; i < 3; i ++) {

if (plField [1][i] == lPlayer && plField [0][i] == lPlayer && plField[2][i] == 0) {
lX = 2;
lY = i;
lCounter ++;
}

if (plField [0][i] == lPlayer && plField [2][i] == lPlayer && plField[1][i] == 0) {
lX = 1;
lY = i;
lCounter ++;
}

if (plField [1][i] == lPlayer && plField [2][i] == lPlayer && plField[0][i] == 0) {
lX = 0;
lY = i;
lCounter ++;
}
}

// Vertical lines
for (i = 0; i < 3; i ++) {

if (plField [i][1] == lPlayer && plField [i][0] == lPlayer && plField[i][2] == 0) {
lX = i;
lY = 2;
lCounter ++;
}

if (plField [i][0] == lPlayer && plField [i][2] == lPlayer && plField[i][1] == 0) {
lX = i;
lY = 1;
lCounter ++;
}

if (plField [i][1] == lPlayer && plField [i][2] == lPlayer && plField[i][0] == 0) {
lX = i;
lY = 0;
lCounter ++;
}
}

// Diagonals
if (plField [1][1] == lPlayer && plField [0][0] == lPlayer && plField[2][2] == 0) {
lX = 2;
lY = 2;
lCounter ++;
}

if (plField [1][1] == lPlayer && plField [2][0] == lPlayer && plField[0][2] == 0) {
lX = 0;
lY = 2;
lCounter ++;
}

if (plField [1][1] == lPlayer && plField [0][2] == lPlayer && plField[2][0] == 0) {
lX = 2;
lY = 0;
lCounter ++;
}

if (plField [1][1] == lPlayer && plField [2][2] == lPlayer && plField[0][0] == 0) {
lX = 0;
lY = 0;
lCounter ++;
}

if (plField [0][0] == lPlayer && plField [2][2] == lPlayer && plField[1][1] == 0) {
lX = 1;
lY = 1;
lCounter ++;
}
if (plField [0][2] == lPlayer && plField [2][0] == lPlayer && plField[1][1] == 0) {
lX = 1;
lY = 1;
lCounter ++;
}

return lCounter;
}


HRESULT CComputer::LoadArray (long lGameID) {

HRESULT hr = S_OK;

IObjectContext* pObjectContext = NULL;

ISharedPropertyGroupManager* spmMgr = NULL;
ISharedPropertyGroup* spmGroup = NULL;
ISharedProperty* spmPropField[3][3];

// Get context
hr = GetObjectContext(&pObjectContext);

// Create the SharedPropertyGroupManager
hr = pObjectContext->CreateInstance (CLSID_SharedPropertyGroupManager, IID_ISharedPropertyGroupManager, (void**)&spmMgr);

// Create the SharedPropertyGroup
LONG lIsolationMode = LockMethod;
LONG lReleaseMode = Process;
VARIANT_BOOL bExists = VARIANT_FALSE;
hr = spmMgr->CreatePropertyGroup (L"TicTacToe", &lIsolationMode, &lReleaseMode, &bExists, &spmGroup);

// Load the field SharedProperties
TCHAR szBuf [512];
BSTR bstrField;
CComVariant vField;

for (long i = 0; i < 3; i ++ ) {
for (long j = 0; j < 3; j ++) {

wsprintf (szBuf, _T("%dField%d%d"), lGameID, i, j);
bstrField = TCHAR2BSTR (szBuf);
hr = spmGroup->CreateProperty (bstrField, &bExists, &spmPropField[i][j]);
::SysFreeString (bstrField);

hr = spmPropField[i][j]->get_Value (&vField);
plField[i][j] = vField.lVal;
}
}

if (pObjectContext)
pObjectContext->Release();

if (spmMgr)
spmMgr->Release();

if (spmGroup)
spmGroup->Release();

for (i = 0; i < 3; i ++) {
for (long j = 0; j < 3; j ++) {
if (spmPropField[i][j]) {
spmPropField[i][j]->Release();
}
}
}

return hr;
}


HRESULT CComputer::SaveArray (long lGameID) {

HRESULT hr = S_OK;

IObjectContext* pObjectContext = NULL;

ISharedPropertyGroupManager* spmMgr = NULL;
ISharedPropertyGroup* spmGroup = NULL;
ISharedProperty* spmPropField[3][3];

// Get context
hr = GetObjectContext(&pObjectContext);

// Create the SharedPropertyGroupManager
hr = pObjectContext->CreateInstance (CLSID_SharedPropertyGroupManager, IID_ISharedPropertyGroupManager, (void**)&spmMgr);

// Create the SharedPropertyGroup
LONG lIsolationMode = LockMethod;
LONG lReleaseMode = Process;
VARIANT_BOOL bExists = VARIANT_FALSE;
hr = spmMgr->CreatePropertyGroup (L"TicTacToe", &lIsolationMode, &lReleaseMode, &bExists, &spmGroup);

// Save the field SharedProperties
TCHAR szBuf [512];
BSTR bstrField;
CComVariant vField;
vField.vt = VT_I4;

for (long i = 0; i < 3; i ++ ) {
for (long j = 0; j < 3; j ++) {

wsprintf (szBuf, _T("%dField%d%d"), lGameID, i, j);
bstrField = TCHAR2BSTR (szBuf);
hr = spmGroup->CreateProperty (bstrField, &bExists, &spmPropField[i][j]);
::SysFreeString (bstrField);

vField.lVal = plField[i][j];
hr = spmPropField[i][j]->put_Value (vField);
}
}

if (pObjectContext)
pObjectContext->Release();

if (spmMgr)
spmMgr->Release();

if (spmGroup)
spmGroup->Release();

for (i = 0; i < 3; i ++) {
for (long j = 0; j < 3; j ++) {
if (spmPropField[i][j]) {
spmPropField[i][j]->Release();
}
}
}

return hr;
}