diff --git a/README.md b/README.md new file mode 100644 index 0000000..22a1b48 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +TicTacToeDialog \ No newline at end of file diff --git a/TicTacToeApp.cpp b/TicTacToeApp.cpp new file mode 100644 index 0000000..e2e4005 --- /dev/null +++ b/TicTacToeApp.cpp @@ -0,0 +1,14 @@ +#include "TicTacToeApp.h" + +#include "TicTacToeDlg.h" + +CTicTacToeApp app; + +BOOL CTicTacToeApp::InitInstance() +{ + CTicTacToeDlg* dlg = new CTicTacToeDlg(); + + dlg->DoModal(); + + return TRUE; +} diff --git a/TicTacToeApp.h b/TicTacToeApp.h new file mode 100644 index 0000000..5c1188e --- /dev/null +++ b/TicTacToeApp.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +class CTicTacToeApp : public CWinApp { +public: + virtual BOOL InitInstance(); +}; diff --git a/TicTacToeDialog.rc b/TicTacToeDialog.rc new file mode 100644 index 0000000..ad69849 --- /dev/null +++ b/TicTacToeDialog.rc @@ -0,0 +1,116 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_TIC_TAC_TOE_DIALOG DIALOGEX 0, 0, 311, 204 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Game from Dmitriy Shishkov" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "Yes",IDOK,199,183,50,14 + PUSHBUTTON "No",IDCANCEL,254,183,50,14 + PUSHBUTTON "",IDC_BUTTON1,107,59,32,29,BS_FLAT + LTEXT "Can you beat this AI in Tic-Tac-Toe?",IDC_STATIC,7,7,297,18 + PUSHBUTTON "",IDC_BUTTON2,139,59,32,29,BS_FLAT + PUSHBUTTON "",IDC_BUTTON3,171,59,32,29,BS_FLAT + PUSHBUTTON "",IDC_BUTTON4,107,88,32,29,BS_FLAT + PUSHBUTTON "",IDC_BUTTON5,139,88,32,29,BS_FLAT + PUSHBUTTON "",IDC_BUTTON6,171,88,32,29,BS_FLAT + PUSHBUTTON "",IDC_BUTTON7,107,117,32,29,BS_FLAT + PUSHBUTTON "",IDC_BUTTON8,139,117,32,29,BS_FLAT + PUSHBUTTON "",IDC_BUTTON9,171,117,32,29,BS_FLAT +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_TIC_TAC_TOE_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 304 + TOPMARGIN, 7 + BOTTOMMARGIN, 197 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_TIC_TAC_TOE_DIALOG AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/TicTacToeDialog.sln b/TicTacToeDialog.sln new file mode 100644 index 0000000..670ac60 --- /dev/null +++ b/TicTacToeDialog.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32210.238 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TicTacToeDialog", "TicTacToeDialog.vcxproj", "{FA0F9820-6113-481C-8A13-11612ACADAD6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FA0F9820-6113-481C-8A13-11612ACADAD6}.Debug|x64.ActiveCfg = Debug|x64 + {FA0F9820-6113-481C-8A13-11612ACADAD6}.Debug|x64.Build.0 = Debug|x64 + {FA0F9820-6113-481C-8A13-11612ACADAD6}.Debug|x86.ActiveCfg = Debug|Win32 + {FA0F9820-6113-481C-8A13-11612ACADAD6}.Debug|x86.Build.0 = Debug|Win32 + {FA0F9820-6113-481C-8A13-11612ACADAD6}.Release|x64.ActiveCfg = Release|x64 + {FA0F9820-6113-481C-8A13-11612ACADAD6}.Release|x64.Build.0 = Release|x64 + {FA0F9820-6113-481C-8A13-11612ACADAD6}.Release|x86.ActiveCfg = Release|Win32 + {FA0F9820-6113-481C-8A13-11612ACADAD6}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8FAD6C10-D566-41D4-8299-9E507E3F8EA8} + EndGlobalSection +EndGlobal diff --git a/TicTacToeDialog.vcxproj b/TicTacToeDialog.vcxproj new file mode 100644 index 0000000..d600e30 --- /dev/null +++ b/TicTacToeDialog.vcxproj @@ -0,0 +1,162 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {d77535d7-7828-4913-91de-08c41d804f8f} + TicTacToeDialog + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + Dynamic + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + + + 0x0419 + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TicTacToeDialog.vcxproj.filters b/TicTacToeDialog.vcxproj.filters new file mode 100644 index 0000000..a54fd11 --- /dev/null +++ b/TicTacToeDialog.vcxproj.filters @@ -0,0 +1,47 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/TicTacToeDlg.cpp b/TicTacToeDlg.cpp new file mode 100644 index 0000000..fe8842e --- /dev/null +++ b/TicTacToeDlg.cpp @@ -0,0 +1,60 @@ +#include "TicTacToeDlg.h" + +void CTicTacToeDlg::OnOK() +{ + if (player == SqState::O) + MessageBox(L"Who are you trying to trick?", L"Game result"); + else + CDialog::OnOK(); +} + +void CTicTacToeDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + + for (UINT i = 0; i < 9; i++) + DDX_Control(pDX, IDC_BUTTON1 + i, buttons[i]); +} + +BEGIN_MESSAGE_MAP(CTicTacToeDlg, CDialog) + ON_CONTROL_RANGE(BN_CLICKED, IDC_BUTTON1, IDC_BUTTON9, CTicTacToeDlg::OnBnClickedSquare) +END_MESSAGE_MAP() + +void CTicTacToeDlg::OnBnClickedSquare(UINT nID) +{ + if (ended) return; + + UINT btn_id = nID - IDC_BUTTON1; + if (field[btn_id] == SqState::E) { + CString msg; + msg.Format(L"Pressed %u button", nID); + TRACE(msg); + + player = SqState::X; + playerMove(btn_id); + buttons[btn_id].SetWindowTextW(L"X"); + + if (checkWin(SqState::X)) { + ended = TRUE; + MessageBox(L"You have been able to do this! It's really cool, because my algorithm must be invincible.", L"Game result"); + return; + } + + player = SqState::O; + + if (checkDraw()) { + MessageBox(L"It's draw. You are still good enogh and didn't do mistakes during the game. This AI is powered by minimax algorithm, so it is invincible by design.", L"Game result"); + return; + } + + UINT comp_id = findComputerMove(); + playerMove(comp_id); + buttons[comp_id].SetWindowTextW(L"O"); + + if (checkWin(SqState::O)) { + ended = TRUE; + MessageBox(L"Unfortunatly, you lost. If it'll make you feel any better, this AI is powered by minimax algorithm, so it is invincible by design."); + return; + } + } +} diff --git a/TicTacToeDlg.h b/TicTacToeDlg.h new file mode 100644 index 0000000..82436e6 --- /dev/null +++ b/TicTacToeDlg.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include "TicTacToeGame.h" +#include "resource.h" + +class CTicTacToeDlg : public CDialog, public TicTacToeGame { +public: + CTicTacToeDlg() : CDialog(IDD_TIC_TAC_TOE_DIALOG, NULL) {}; + +private: + void OnOK(); + void DoDataExchange(CDataExchange* pDX); + + CButton buttons[9]; + + DECLARE_MESSAGE_MAP() + afx_msg void OnBnClickedSquare(UINT nID); +}; diff --git a/TicTacToeGame.cpp b/TicTacToeGame.cpp new file mode 100644 index 0000000..ba64ee2 --- /dev/null +++ b/TicTacToeGame.cpp @@ -0,0 +1,126 @@ +#include "TicTacToeGame.h" + +/** + * @brief Sets field cell to current player + * @param coord - Cell coordinate + */ +void TicTacToeGame::playerMove(UINT coord) +{ + field[coord] = player; +} + +/** + * @brief Calculates best move for computer using minimax algorithm + */ +UINT TicTacToeGame::findComputerMove() +{ + INT bestScore = INT_MIN, score; + UINT bestCell = -1; + + for (UINT i = 0; i < 9; i++) + if (field[i] == SqState::E) { + field[i] = SqState::O; + + score = minimax(0, SqState::O); + + if (score > bestScore) { + bestScore = score; + bestCell = i; + } + + field[i] = SqState::E; + } + + return bestCell; +} + +/** + * @brief Calculates score of current game board for player. If the game is ended, it returns its result minus depth of this state in game tree. Else, it returns + * @param depth - Number of steps made in game graph + * @param player - Player for which we want to calculate current score + */ +INT TicTacToeGame::minimax(UINT depth, SqState player) +{ + if (player == SqState::E) throw "Called with Empty player"; + + SqState enemy = getEnemy(player); + + INT winScore = checkWin(player); + + if (winScore) return winScore - depth; + + INT maxEnemyScore = INT_MIN; + + for (int i = 0; i < 9; i++) + if (field[i] == SqState::E) { + field[i] = enemy; + INT score = minimax(depth + 1, enemy); + + if (score > maxEnemyScore) + maxEnemyScore = score; + + field[i] = SqState::E; + } + + if (maxEnemyScore == INT_MIN) return 0; + + return -maxEnemyScore; +} + +/** + * @brief Checks if current game state is wining for player or its enemy + * @param player - Player for which we check + * @returns 10 means win for player, -10 - for enemy, 0 - draw or unfinished + */ +INT TicTacToeGame::checkWin(SqState player) +{ + if (player == SqState::E) throw "Called with Empty player"; + + SqState enemy = getEnemy(player); + + for (INT i = 0; i < 3; i++) { + if ((field[i * 3] == field[1 + i * 3] && field[1 + i * 3] == field[2 + i * 3]) || // Row + (field[i] == field[3 + i] && field[3 + i] == field[6 + i])) { // Column + if (field[i * 4] == player) + return 10; + if (field[i * 4] == enemy) + return -10; + } + } + + if ((field[0] == field[4] && field[4] == field[8]) || // first diagonal + (field[2] == field[4] && field[4] == field[6])) { // second diagonal + if (field[4] == player) + return 10; + if (field[4] == enemy) + return -10; + } + + return 0; +} + +/** + * @brief Checks if current game state is a draw + */ +BOOL TicTacToeGame::checkDraw() +{ + for (int i = 0; i < 9; i++) + if (field[i] == SqState::E) + return FALSE; + + ended = TRUE; + return TRUE; +} + +/** + * @brief Utility for getting enemy of player + */ +SqState TicTacToeGame::getEnemy(SqState player) +{ + if (player == SqState::E) throw "Called with Empty player"; + + if (player == SqState::X) + return SqState::O; + else + return SqState::X; +} diff --git a/TicTacToeGame.h b/TicTacToeGame.h new file mode 100644 index 0000000..a665863 --- /dev/null +++ b/TicTacToeGame.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +enum class SqState { + E = -1, X, O +}; + +class TicTacToeGame { +public: + SqState field[9] = { SqState::E, SqState::E, SqState::E, SqState::E, SqState::E, SqState::E, SqState::E, SqState::E, SqState::E }; + SqState player = SqState::X; + BOOL ended = FALSE; + + void playerMove(UINT coord); + UINT findComputerMove(); + + INT minimax(UINT depth, SqState player); + INT checkWin(SqState player); + BOOL checkDraw(); + SqState getEnemy(SqState player); +}; diff --git a/resource.h b/resource.h new file mode 100644 index 0000000..513d2ba --- /dev/null +++ b/resource.h @@ -0,0 +1,25 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by TicTacToeDialog.rc +// +#define IDD_TIC_TAC_TOE_DIALOG 101 +#define IDC_BUTTON1 1001 +#define IDC_BUTTON2 1002 +#define IDC_BUTTON3 1003 +#define IDC_BUTTON4 1004 +#define IDC_BUTTON5 1005 +#define IDC_BUTTON6 1006 +#define IDC_BUTTON7 1007 +#define IDC_BUTTON8 1008 +#define IDC_BUTTON9 1009 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 106 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1005 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif