From 72c46bdd7f5d430ab1ad1d420ed77c7f22df857a Mon Sep 17 00:00:00 2001 From: Stanislaw Halik Date: Sat, 30 Sep 2017 15:01:35 +0200 Subject: rename --- .../ActivatableBoardGame/ActivatableBoardGame.cpp | 54 +++ .../ActivatableBoardGame/ActivatableBoardGame.ico | Bin 0 -> 2998 bytes .../ActivatableBoardGame/ActivatableBoardGame.rc | Bin 0 -> 8056 bytes .../ActivatableBoardGame.vcxproj | 193 ++++++++ .../ActivatableBoardGame.vcxproj.filters | 70 +++ Tobii-EyeX/samples/ActivatableBoardGame/Board.cpp | 220 ++++++++++ Tobii-EyeX/samples/ActivatableBoardGame/Board.h | 110 +++++ .../samples/ActivatableBoardGame/BoardWindow.cpp | 488 +++++++++++++++++++++ .../samples/ActivatableBoardGame/BoardWindow.h | 94 ++++ .../ActivatableBoardGame.lastbuildstate | 2 + .../Debug/Activata.2732E876.tlog/CL.command.1.tlog | Bin 0 -> 4002 bytes .../Debug/Activata.2732E876.tlog/CL.read.1.tlog | Bin 0 -> 97924 bytes .../Debug/Activata.2732E876.tlog/CL.write.1.tlog | Bin 0 -> 2792 bytes .../Activata.2732E876.tlog/link.command.1.tlog | Bin 0 -> 2284 bytes .../Debug/Activata.2732E876.tlog/link.read.1.tlog | Bin 0 -> 4972 bytes .../Debug/Activata.2732E876.tlog/link.write.1.tlog | Bin 0 -> 1100 bytes .../Debug/Activata.2732E876.tlog/rc.command.1.tlog | Bin 0 -> 432 bytes .../Debug/Activata.2732E876.tlog/rc.read.1.tlog | Bin 0 -> 2714 bytes .../Debug/Activata.2732E876.tlog/rc.write.1.tlog | Bin 0 -> 286 bytes .../Debug/ActivatableBoardGame.log | 8 + .../Debug/ActivatableBoardGame.obj | Bin 0 -> 131239 bytes .../Debug/ActivatableBoardGame.pch | Bin 0 -> 8323072 bytes .../Debug/ActivatableBoardGame.res | Bin 0 -> 7312 bytes .../samples/ActivatableBoardGame/Debug/Board.obj | Bin 0 -> 257029 bytes .../ActivatableBoardGame/Debug/BoardWindow.obj | Bin 0 -> 348456 bytes .../ActivatableBoardGame/Debug/EyeXHost.obj | Bin 0 -> 353721 bytes .../samples/ActivatableBoardGame/Debug/stdafx.obj | Bin 0 -> 142759 bytes .../samples/ActivatableBoardGame/Debug/vc140.idb | Bin 0 -> 1330176 bytes .../samples/ActivatableBoardGame/Debug/vc140.pdb | Bin 0 -> 1552384 bytes .../samples/ActivatableBoardGame/EyeXHost.cpp | 377 ++++++++++++++++ Tobii-EyeX/samples/ActivatableBoardGame/EyeXHost.h | 111 +++++ Tobii-EyeX/samples/ActivatableBoardGame/Observer.h | 13 + Tobii-EyeX/samples/ActivatableBoardGame/Resource.h | Bin 0 -> 2126 bytes Tobii-EyeX/samples/ActivatableBoardGame/small.ico | Bin 0 -> 2998 bytes Tobii-EyeX/samples/ActivatableBoardGame/stdafx.cpp | 3 + Tobii-EyeX/samples/ActivatableBoardGame/stdafx.h | 17 + .../samples/ActivatableBoardGame/targetver.h | 8 + 37 files changed, 1768 insertions(+) create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.cpp create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.ico create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.rc create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.vcxproj create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.vcxproj.filters create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Board.cpp create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Board.h create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/BoardWindow.cpp create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/BoardWindow.h create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/ActivatableBoardGame.lastbuildstate create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/CL.command.1.tlog create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/CL.read.1.tlog create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/CL.write.1.tlog create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/link.command.1.tlog create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/link.read.1.tlog create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/link.write.1.tlog create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/rc.command.1.tlog create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/rc.read.1.tlog create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/rc.write.1.tlog create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/ActivatableBoardGame.log create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/ActivatableBoardGame.obj create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/ActivatableBoardGame.pch create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/ActivatableBoardGame.res create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/Board.obj create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/BoardWindow.obj create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/EyeXHost.obj create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/stdafx.obj create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/vc140.idb create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Debug/vc140.pdb create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/EyeXHost.cpp create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/EyeXHost.h create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Observer.h create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/Resource.h create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/small.ico create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/stdafx.cpp create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/stdafx.h create mode 100755 Tobii-EyeX/samples/ActivatableBoardGame/targetver.h (limited to 'Tobii-EyeX/samples/ActivatableBoardGame') diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.cpp b/Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.cpp new file mode 100755 index 0000000..4fbe055 --- /dev/null +++ b/Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.cpp @@ -0,0 +1,54 @@ +/* + * ActivatableBoardGame sample: + * This is an example that demonstrates the use of Activatable interactors in the context of a board game. + * + * Copyright 2013 Tobii Technology AB. All rights reserved. + */ + +#include "stdafx.h" +#include +#include +#include +#include "Resource.h" +#include "Board.h" +#include "BoardWindow.h" + +#pragma comment (lib, "Gdiplus.lib") + +// Application entry point. +int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, + _In_opt_ HINSTANCE hPrevInstance, + _In_ LPTSTR lpCmdLine, + _In_ int nCmdShow) +{ + UNREFERENCED_PARAMETER(hPrevInstance); + UNREFERENCED_PARAMETER(lpCmdLine); + + // initialize the GDI+ library. + ULONG_PTR gdiplusToken; + Gdiplus::GdiplusStartupInput gdiplusStartupInput; + Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); + + // initialize the Board and the BoardWindow. + // these will be un-initialized automatically when they fall out of scope. + Board board(8); + BoardWindow::RegisterWindowClass(hInstance); + BoardWindow window(board, hInstance, nCmdShow); + + HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_GOBANG)); + + // Main message loop + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) + { + if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + Gdiplus::GdiplusShutdown(gdiplusToken); + + return (int) msg.wParam; +} diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.ico b/Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.ico new file mode 100755 index 0000000..449296f Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.ico differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.rc b/Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.rc new file mode 100755 index 0000000..718dee1 Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.rc differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.vcxproj b/Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.vcxproj new file mode 100755 index 0000000..5b82b9c --- /dev/null +++ b/Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.vcxproj @@ -0,0 +1,193 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {2732E876-973B-4453-AA9F-D306EFB11922} + Win32Proj + ActivatableBoardGame + + + + Application + true + v140 + Unicode + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + Application + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + ..\..\include;%(AdditionalIncludeDirectories) + + + Windows + true + ..\..\lib\x86 + + + true + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + ..\..\include;%(AdditionalIncludeDirectories) + + + Windows + true + ..\..\lib\x64 + + + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + ..\..\include;%(AdditionalIncludeDirectories) + + + Windows + true + true + true + ..\..\lib\x86 + + + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + ..\..\include;%(AdditionalIncludeDirectories) + + + Windows + true + true + true + ..\..\lib\x64 + + + true + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.vcxproj.filters b/Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.vcxproj.filters new file mode 100755 index 0000000..9baf12a --- /dev/null +++ b/Tobii-EyeX/samples/ActivatableBoardGame/ActivatableBoardGame.vcxproj.filters @@ -0,0 +1,70 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;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 + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Resource Files + + + + + Resource Files + + + Resource Files + + + \ No newline at end of file diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Board.cpp b/Tobii-EyeX/samples/ActivatableBoardGame/Board.cpp new file mode 100755 index 0000000..e59fc92 --- /dev/null +++ b/Tobii-EyeX/samples/ActivatableBoardGame/Board.cpp @@ -0,0 +1,220 @@ +/* + * ActivatableBoardGame sample: + * This is an example that demonstrates the use of Activatable interactors in the context of a board game. + * + * Copyright 2013 Tobii Technology AB. All rights reserved. + */ + +#include "stdafx.h" +#include "Board.h" +#include +#include +#include "Observer.h" + +// initialize the random number generator. +auto const seed = std::random_device()(); +std::mt19937 Board::_randomNumberGenerator(seed); + +Board::Board(int size) + : _size(size), _board(nullptr), _boardChangedObserver(nullptr) +{ + _board = new Marker[_size * _size]; + BeginNewGame(); +} + +Board::~Board() +{ + delete[] _board; +} + +void Board::InitBoard() +{ + for (int i = 0; i < _size; ++i) + { + for (int j = 0; j < _size; ++j) + { + SetMarkerAt(Position(i, j), Marker::None); + } + } + + SetMarkerAt(Position((_size - 1) / 2, (_size - 1) / 2), Marker::X); + SetMarkerAt(Position((_size - 1) / 2, (_size - 1) / 2 + 1), Marker::O); + SetMarkerAt(Position((_size - 1) / 2 + 1, (_size - 1) / 2), Marker::O); + SetMarkerAt(Position((_size - 1) / 2 + 1, (_size - 1) / 2 + 1), Marker::X); +} + +Board::Marker Board::GetMarkerAt(Position position) const +{ + assert(IsValidPosition(position)); + + return _board[position.column + position.row * _size]; +} + +void Board::SetMarkerAt(Position position, Marker marker) +{ + assert(IsValidPosition(position)); + + _board[position.column + position.row * _size] = marker; + + NotifyObserver(); +} + +void Board::MakeHumanPlayerMove(Position position) +{ + if (_playerInTurn == Marker::X && + !IsGameOver() && + GetMarkerAt(position) == Marker::None) + { + MakeMove(position); + + if (!IsGameOver()) + { + MakeAIPlayerMove(); + } + } +} + +bool Board::CanMakeMoveAt(Position position) const +{ + return IsValidPosition(position) && + GetMarkerAt(position) == Marker::None; +} + +void Board::BeginNewGame() +{ + InitBoard(); + _playerInTurn = Marker::X; + _winner = Marker::None; +} + +bool Board::IsValidPosition(Position position) const +{ + return 0 <= position.row && position.row < _size && + 0 <= position.column && position.column < _size; +} + +void Board::MakeAIPlayerMove() +{ + // generate the list of all possible moves. not that they are good or anything, just possible. + std::vector possibleMoves; + for (int i = 0; i < _size; i++) + { + for (int j = 0; j < _size; j++) + { + Position position(i, j); + if (GetMarkerAt(position) == Marker::None) + { + possibleMoves.push_back(position); + } + } + } + + // since we check whether the game is a draw before calling this method, there should always be at least one possible move. + assert(possibleMoves.size() > 0); + + // pick a move at random. + std::uniform_int_distribution idist(0, (int)possibleMoves.size() - 1); + int randomMoveIndex = idist(_randomNumberGenerator); + MakeMove(possibleMoves[randomMoveIndex]); +} + +void Board::MakeMove(Position position) +{ + SetMarkerAt(position, _playerInTurn); + _playerInTurn = (_playerInTurn == Marker::O) ? Marker::X : Marker::O; + + DetectWinner(position); + DetectDrawGame(); +} + +void Board::DetectWinner(Position position) +{ + auto marker = GetMarkerAt(position); + + for (int i = 0; i < OrientationMaxValue; ++i) + { + Orientation orientation = (Orientation)i; + int sequenceLength(1); + + bool directions[] = { true, false }; + + for each (auto forward in directions) + { + auto neighbor = GetAdjacentPosition(position, orientation, forward); + while (IsValidPosition(neighbor) && GetMarkerAt(neighbor) == marker) + { + sequenceLength++; + neighbor = GetAdjacentPosition(neighbor, orientation, forward); + } + } + + if (sequenceLength >= WinningSequenceLength) + { + _winner = marker; + _playerInTurn = Marker::None; + NotifyObserver(); + } + } +} + +void Board::DetectDrawGame() +{ + // if there is an empty position, the game isn't a draw (yet). + for (int i = 0; i < _size; ++i) + { + for (int j = 0; j < _size; ++j) + { + if (GetMarkerAt(Position(i, j)) == Marker::None) + { + return; + } + } + } + + // no empty positions found: it's a draw. + _playerInTurn = Marker::None; + NotifyObserver(); +} + +Board::Position Board::GetAdjacentPosition(Position position, Orientation orientation, bool forward) const +{ + Position delta(-1, -1); + switch (orientation) + { + case Orientation::North: + delta = Position(+1, 0); + break; + case Orientation::East: + delta = Position(0, +1); + break; + case Orientation::Northeast: + delta = Position(+1, +1); + break; + case Orientation::Southeast: + delta = Position(-1, +1); + break; + default: + assert(false); + } + + if (!forward) + { + delta.row = -delta.row; + delta.column = -delta.column; + } + + return Position(position.row + delta.row, position.column + delta.column); +} + +void Board::RegisterBoardChangedObserver(Observer* boardChangedObserver) +{ + _boardChangedObserver = boardChangedObserver; +} + +void Board::NotifyObserver() +{ + if (_boardChangedObserver) + { + _boardChangedObserver->SubjectChanged(); + } +} diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Board.h b/Tobii-EyeX/samples/ActivatableBoardGame/Board.h new file mode 100755 index 0000000..d6abbe3 --- /dev/null +++ b/Tobii-EyeX/samples/ActivatableBoardGame/Board.h @@ -0,0 +1,110 @@ +/* + * Board class: Implements the game logic and the not-so-clever AI player for the exciting Gobang game. + * + * Copyright 2013 Tobii Technology AB. All rights reserved. + */ + +#pragma once + +#include + +class Observer; + +class Board +{ +public: + // Represents a position on the board. + struct Position + { + int row; + int column; + + Position(int paramRow, int paramColumn) : row(paramRow), column(paramColumn) { } + }; + + // Possible contents of a board position. + enum Marker + { + None, + X, + O + }; + + Board(int size); + virtual ~Board(); + + // gets the size of the board, which is assumed to be square: the number of positions on each side. + int Size() const { return _size; } + + // indicates whether the game is over. + bool IsGameOver() const { return _playerInTurn == Marker::None; } + + // gets the "name" of the lucky winner. + Marker GetWinner() const { return _winner; } + + // gets what's on the board at a given position. + Marker GetMarkerAt(Position position) const; + + // indicates whether it is possible to place a marker at a given position. + bool CanMakeMoveAt(Position position) const; + + // makes a move for the human player (which will also trigger an AI move). + void MakeHumanPlayerMove(Position position); + + // restarts the game. + void BeginNewGame(); + + // registers an observer that is notified when the board has changed. + void RegisterBoardChangedObserver(Observer* boardChangedObserver); + +private: + enum Orientation + { + North, + East, + Northeast, + Southeast, + OrientationMaxValue + }; + + // tests whether a position is on the board. + bool IsValidPosition(Position position) const; + + // gets a position adjacent to the given one, in a particular direction. + Position GetAdjacentPosition(Position position, Orientation orientation, bool forward) const; + + // prepares the board for a new, exciting game. + void InitBoard(); + + // lets the miserable AI player make a move. + void MakeAIPlayerMove(); + + // makes a move: places a marker and checks whether anyone has won or if it's a draw game. + void MakeMove(Position position); + + // places a marker at the given position. + void SetMarkerAt(Position position, Marker marker); + + // checks if the given position is part of a winning sequence. + void DetectWinner(Position position); + + // checks if the game is a draw. + void DetectDrawGame(); + + // notifies the observer, if any, that the board has changed. + void NotifyObserver(); + + static const int WinningSequenceLength = 5; + + static std::mt19937 _randomNumberGenerator; + + int _size; + Marker* _board; + Marker _playerInTurn; + Marker _winner; + Observer* _boardChangedObserver; + + // private copy constructor and operator making the class non-copyable (declared but not implemented). + Board(const Board&); + Board& operator = (const Board&); +}; diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/BoardWindow.cpp b/Tobii-EyeX/samples/ActivatableBoardGame/BoardWindow.cpp new file mode 100755 index 0000000..7f8b9a7 --- /dev/null +++ b/Tobii-EyeX/samples/ActivatableBoardGame/BoardWindow.cpp @@ -0,0 +1,488 @@ +/* + * ActivatableBoardGame sample: + * This is an example that demonstrates the use of Activatable interactors in the context of a board game. + * + * Copyright 2013 Tobii Technology AB. All rights reserved. + */ + +#include "stdafx.h" +#include "BoardWindow.h" +#include "Board.h" +#include "resource.h" + +// window messages used for notifications from the EyeXHost. +#define WM_EYEX_HOST_STATUS_CHANGED WM_USER + 0 +#define WM_FOCUSED_REGION_CHANGED WM_USER + 1 +#define WM_REGION_ACTIVATED WM_USER + 2 + +// color scheme. +const Gdiplus::Color BoardWindow::BackgroundGazeInteractionFunctionalColor = Gdiplus::Color(134, 152, 172); // blue +const Gdiplus::Color BoardWindow::BackgroundMouseOnlyColor = Gdiplus::Color(164, 156, 153); // grey +const Gdiplus::Color BoardWindow::GridColor = Gdiplus::Color(110, 57, 88, 109); // dark blue +const Gdiplus::Color BoardWindow::MarkerColor = Gdiplus::Color(242, 233, 216); // off white +const Gdiplus::Color BoardWindow::HighlightColor = Gdiplus::Color(20, 242, 233, 216); // transparent off white +const Gdiplus::Color BoardWindow::WonMessageColor = Gdiplus::Color(200, 64, 82, 60); // transparent dark green +const Gdiplus::Color BoardWindow::LostMessageColor = Gdiplus::Color(200, 155, 109, 135); // transparent grape + +// constants. +const int BoardWindow::SquareSize = 100; +const int BoardWindow::MarkerMargin = 14; +const int BoardWindow::MarkerDiameter = SquareSize - MarkerMargin * 2; +const TCHAR* BoardWindow::WindowClassName = _T("GobangBoard"); + +BoardWindow::BoardWindow(Board& board, HINSTANCE hInstance, int nCmdShow) + : _board(board), _hInstance(hInstance), _hWnd(0), _hMenu(0), _leftMargin(0), _upperMargin(0) +{ + // create the window instance. + _hWnd = CreateWindow( + WindowClassName, + _T("Gobang!"), + WS_OVERLAPPED | WS_MINIMIZEBOX | WS_SYSMENU, + CW_USEDEFAULT, + CW_USEDEFAULT, + (_board.Size() + 1) * SquareSize, + (_board.Size() + 1) * SquareSize + 40, + NULL, + NULL, + hInstance, + NULL); + + if (!_hWnd) + { + throw new std::runtime_error("Could not create main window."); + } + + _hMenu = GetMenu(_hWnd); + + // write the "this" pointer to the extra window memory so that we can reference back to this + // object from the window function. + SetWindowLongPtr(_hWnd, 0, (LONG_PTR)this); + + ShowWindow(_hWnd, nCmdShow); + UpdateWindow(_hWnd); + + _board.RegisterBoardChangedObserver(this); + + // initialize the EyeX host and the activatable regions. + _eyeXHost.Init(_hWnd, WM_EYEX_HOST_STATUS_CHANGED, WM_FOCUSED_REGION_CHANGED, WM_REGION_ACTIVATED); + UpdateActivatableRegions(); +} + +BoardWindow::~BoardWindow() +{ +} + +void BoardWindow::RegisterWindowClass(HINSTANCE hInstance) +{ + WNDCLASSEX wcex; + + wcex.cbSize = sizeof(WNDCLASSEX); + + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = sizeof(void*); // make room for the "this" pointer. + wcex.hInstance = hInstance; + wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_GOBANG)); + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = 0; + wcex.lpszMenuName = MAKEINTRESOURCE(IDC_GOBANG); + wcex.lpszClassName = WindowClassName; + wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); + + RegisterClassEx(&wcex); +} + +// Callback function invoked when the Board has changed. +void BoardWindow::SubjectChanged() +{ + InvalidateRect(_hWnd, NULL, FALSE); + UpdateActivatableRegions(); +} + +// Callback function invoked from the message loop to process a Windows message. +LRESULT CALLBACK BoardWindow::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + int wmId, wmEvent; + PAINTSTRUCT ps; + HDC hdc; + + // read the "this" pointer from extra window memory. + BoardWindow* instance = reinterpret_cast(GetWindowLongPtr(hWnd, 0)); + + switch (message) + { + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + + // Parse the menu selections: + switch (wmId) + { + case IDM_NEWGAME: + instance->OnNewGameClicked(); + break; + + case IDM_ABOUT: + DialogBox(instance->_hInstance, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); + break; + + case IDM_EXIT: + DestroyWindow(hWnd); + break; + + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + break; + + case WM_LBUTTONDOWN: + { + // mouse button pressed; possibly on a board square. + auto point = MAKEPOINTS(lParam); + instance->OnSquareClicked(point); + } + break; + + case WM_PAINT: + instance->UpdateMargins(); + hdc = BeginPaint(hWnd, &ps); + instance->OnDraw(hdc); + EndPaint(hWnd, &ps); + break; + + case WM_ERASEBKGND: + // no background erasing needed since our OnDraw method draws the entire window. + return TRUE; + + case WM_EYEX_HOST_STATUS_CHANGED: + // the background color of the board indicates whether we have a working connection to the engine or not. + // so, when the status changes, we force a redraw of the window. + InvalidateRect(hWnd, 0, FALSE); + break; + + case WM_FOCUSED_REGION_CHANGED: + // redraw to display the new focus state. + InvalidateRect(hWnd, 0, FALSE); + break; + + case WM_REGION_ACTIVATED: + instance->OnSquareActivated((UINT)wParam); + break; + + case WM_WINDOWPOSCHANGED: + // the window was moved, so we need to refresh the screen bounds of our activatable regions. + instance->UpdateActivatableRegions(); + break; + + case WM_DESTROY: + PostQuitMessage(0); + break; + case WM_KEYUP: + // trigger an activation command when space is released. + if (VK_SPACE == wParam) + { + instance->_eyeXHost.TriggerActivation(); + } + break; + + case WM_KEYDOWN: + // trigger activation mode on when space is pressed down (so activation focus changed events are received, and a highlight can be drawn). + if (VK_SPACE == wParam) + { + instance->_eyeXHost.TriggerActivationModeOn(); + } + break; + + + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + + return 0; +} + +void BoardWindow::OnDraw(HDC hdc) +{ + // use double-buffer drawing to speed up the drawing and reduce flicker. + // (the drawing is performed on a bitmap, which is then blitted to the screen in one fell swoop.) + + RECT rect; + GetClientRect(_hWnd, &rect); + Gdiplus::Bitmap bitmap(rect.right - rect.left, rect.bottom - rect.top); + Gdiplus::Graphics graphics(&bitmap); + + // draw the background in a color which depends on the state of the connection to the engine. + // blue if the connection is functional, gray otherwise. + Gdiplus::Color backgroundColor(BackgroundGazeInteractionFunctionalColor); + if (!_eyeXHost.IsFunctional()) + { + backgroundColor = BackgroundMouseOnlyColor; + } + Gdiplus::SolidBrush backgroundBrush(backgroundColor); + graphics.FillRectangle(&backgroundBrush, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); + + // draw the grid + DrawGrid(&graphics); + + // draw the markers + Gdiplus::Pen markerPen(MarkerColor, 5.0F); + for (int row = 0; row < _board.Size(); row++) + { + for (int col = 0; col < _board.Size(); col++) + { + switch (_board.GetMarkerAt(Board::Position(row, col))) + { + case Board::Marker::X: + DrawXMarker(&graphics, &markerPen, row, col); + break; + + case Board::Marker::O: + DrawOMarker(&graphics, &markerPen, row, col); + break; + } + } + } + + // draw the focus highlight + int focusedRegionId = _eyeXHost.GetFocusedRegionId(); + if (focusedRegionId >= 0) + { + DrawHighlight(&graphics, RegionIdToBoardRow(focusedRegionId), RegionIdToBoardColumn(focusedRegionId)); + } + + // draw the game over message + if (_board.IsGameOver()) + { + WCHAR* message; + Gdiplus::Color messageColor(0,0,0); + switch (_board.GetWinner()) + { + case Board::Marker::X: + message = L"YOU WON :)"; + messageColor = WonMessageColor; + break; + + case Board::Marker::O: + message = L"YOU LOST :("; + messageColor = LostMessageColor; + break; + + default: + message = L"DRAW"; + messageColor = LostMessageColor; + break; + } + + DrawGameOver(&graphics, message, messageColor); + } + + Gdiplus::Graphics screen(hdc); + screen.DrawImage(&bitmap, 0, 0); +} + +void BoardWindow::DrawGrid(Gdiplus::Graphics *graphics) +{ + const int lineMargin = 14; + + Gdiplus::Pen gridPen(GridColor); + gridPen.SetDashStyle(Gdiplus::DashStyleDash); + + // horizontal lines + auto x0 = _leftMargin - lineMargin; + auto x1 = _leftMargin + _board.Size() * SquareSize + lineMargin; + for (int i = 0; i <= _board.Size(); i++) + { + auto y = _upperMargin + i * SquareSize; + graphics->DrawLine(&gridPen, x0, y, x1, y); + } + + // vertical lines + auto y0 = _upperMargin - lineMargin; + auto y1 = _upperMargin + _board.Size() * SquareSize + lineMargin; + for (int j = 0; j <= _board.Size(); j++) + { + auto x = _leftMargin + j * SquareSize; + graphics->DrawLine(&gridPen, x, y0, x, y1); + } + + // dots that guide the eye to the center of the square + Gdiplus::SolidBrush gridBrush(GridColor); + for (int i = 0; i <= _board.Size(); i++) + { + for (int j = 0; j <= _board.Size(); j++) + { + auto x = _leftMargin + (j + 0.5) * SquareSize; + auto y = _upperMargin + (i + 0.5) * SquareSize; + graphics->FillEllipse(&gridBrush, (Gdiplus::REAL)(x - 1), (Gdiplus::REAL)(y - 1), (Gdiplus::REAL)3, (Gdiplus::REAL)3); + } + } +} + +void BoardWindow::DrawHighlight(Gdiplus::Graphics *graphics, INT row, INT column) +{ + Gdiplus::SolidBrush highlightBrush(HighlightColor); + + int x = BoardToClientAreaX(column) + 1; + int y = BoardToClientAreaY(row) + 1; + + graphics->FillRectangle(&highlightBrush, x, y, SquareSize - 1, SquareSize - 1); +} + +void BoardWindow::DrawOMarker(Gdiplus::Graphics *graphics, Gdiplus::Pen *markerPen, INT row, INT column) +{ + int x = BoardToClientAreaX(column) + MarkerMargin; + int y = BoardToClientAreaY(row) + MarkerMargin; + + graphics->DrawEllipse(markerPen, x, y, MarkerDiameter, MarkerDiameter); +} + +void BoardWindow::DrawXMarker(Gdiplus::Graphics *graphics, Gdiplus::Pen *markerPen, INT row, INT column) +{ + int x = BoardToClientAreaX(column) + MarkerMargin; + int y = BoardToClientAreaY(row) + MarkerMargin; + + graphics->DrawLine(markerPen, x, y + MarkerDiameter, x + MarkerDiameter, y); + graphics->DrawLine(markerPen, x, y, x + MarkerDiameter, y + MarkerDiameter); +} + +void BoardWindow::DrawGameOver(Gdiplus::Graphics *graphics, WCHAR* message, const Gdiplus::Color& messageColor) +{ + Gdiplus::Font myFont(L"Arial", 72); + + RECT rect; + GetClientRect(_hWnd, &rect); + Gdiplus::RectF layoutRect((float)rect.left, (float)rect.top, (float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); + + Gdiplus::StringFormat format; + format.SetAlignment(Gdiplus::StringAlignmentCenter); + format.SetLineAlignment(Gdiplus::StringAlignmentCenter); + + Gdiplus::SolidBrush messageBrush(messageColor); + + graphics->DrawString( + message, + (INT)wcslen(message), + &myFont, + layoutRect, + &format, + &messageBrush); +} + +INT BoardWindow::BoardToClientAreaX(INT column) +{ + return column * SquareSize + _leftMargin; +} + +INT BoardWindow::BoardToClientAreaY(INT row) +{ + return row * SquareSize + _upperMargin; +} + +INT BoardWindow::ClientAreaToBoardColumn(INT x) +{ + return (x - _upperMargin) / SquareSize; +} + +INT BoardWindow::ClientAreaToBoardRow(INT y) +{ + return (y - _leftMargin) / SquareSize; +} + +int BoardWindow::BoardPositionToRegionId(int row, int column) +{ + return column + row * _board.Size(); +} + +int BoardWindow::RegionIdToBoardRow(int regionId) +{ + return regionId / _board.Size(); +} + +int BoardWindow::RegionIdToBoardColumn(int regionId) +{ + return regionId % _board.Size(); +} + +void BoardWindow::UpdateMargins() +{ + RECT rect; + GetClientRect(_hWnd, &rect); + _upperMargin = ((rect.bottom - rect.top) - _board.Size() * SquareSize) / 2; + _leftMargin = ((rect.right - rect.left) - _board.Size() * SquareSize) / 2; +} + +void BoardWindow::OnSquareClicked(POINTS point) +{ + MakeHumanPlayerMove(ClientAreaToBoardRow(point.y), ClientAreaToBoardColumn(point.x)); +} + +void BoardWindow::OnSquareActivated(UINT regionId) +{ + MakeHumanPlayerMove(RegionIdToBoardRow(regionId), RegionIdToBoardColumn(regionId)); +} + +void BoardWindow::MakeHumanPlayerMove(int row, int column) +{ + Board::Position position(row, column); + if (_board.CanMakeMoveAt(position)) + { + _board.MakeHumanPlayerMove(position); + } +} + +void BoardWindow::UpdateActivatableRegions() +{ + std::vector regions; + + if (!_board.IsGameOver()) + { + for (int row = 0; row < _board.Size(); row++) + { + for (int column = 0; column < _board.Size(); column++) + { + POINT point; + point.x = BoardToClientAreaX(column); + point.y = BoardToClientAreaY(row); + ClientToScreen(_hWnd, &point); + + RECT bounds; + bounds.left = point.x; + bounds.top = point.y; + bounds.right = point.x + SquareSize; + bounds.bottom = point.y + SquareSize; + + regions.push_back(EyeXHost::ActivatableRegion(BoardPositionToRegionId(row, column), bounds)); + } + } + } + + _eyeXHost.SetActivatableRegions(regions); +} + +void BoardWindow::OnNewGameClicked() +{ + _board.BeginNewGame(); + UpdateActivatableRegions(); +} + +// Message handler for about box. +INT_PTR CALLBACK BoardWindow::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; +} diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/BoardWindow.h b/Tobii-EyeX/samples/ActivatableBoardGame/BoardWindow.h new file mode 100755 index 0000000..50dc346 --- /dev/null +++ b/Tobii-EyeX/samples/ActivatableBoardGame/BoardWindow.h @@ -0,0 +1,94 @@ +/* + * BoardWindow class: Responsible for the game window and the GDI+ drawing. + * Listens to notifications from the Board. + * Owns the EyeX host. + * + * Copyright 2013 Tobii Technology AB. All rights reserved. + */ + +#pragma once + +#include +#include +#include +#include "Observer.h" +#include "EyeXHost.h" + +class Board; + +class BoardWindow : Observer +{ +public: + BoardWindow(Board& board, HINSTANCE hInstance, int nCmdShow); + virtual ~BoardWindow(); + + // registers the window class; must be called once at application initialization. + static void RegisterWindowClass(HINSTANCE hInstance); + + // From Observer + void SubjectChanged(); + +private: + // window procedure for the main window. + static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); + + // window procedure for the About dialog. + static INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); + + // GDI+ drawing methods. + void OnDraw(HDC hdc); + void DrawGrid(Gdiplus::Graphics *graphics); + void DrawHighlight(Gdiplus::Graphics* graphics, int row, int column); + void DrawXMarker(Gdiplus::Graphics* graphics, Gdiplus::Pen* pen, int row, int column); + void DrawOMarker(Gdiplus::Graphics* graphics, Gdiplus::Pen* pen, int row, int column); + void DrawGameOver(Gdiplus::Graphics* graphics, WCHAR* message, const Gdiplus::Color& messageColor); + + // coordinate/board position mapping methods. + int BoardToClientAreaX(int column); + int BoardToClientAreaY(int row); + int ClientAreaToBoardColumn(int x); + int ClientAreaToBoardRow(int y); + + // region id/board position mapping methods. + int BoardPositionToRegionId(int row, int column); + int RegionIdToBoardRow(int regionId); + int RegionIdToBoardColumn(int regionId); + + // re-calculates the margins based on the size of the window. + void UpdateMargins(); + + // reports all vacant board positions as activatable regions to the EyeX host. + void UpdateActivatableRegions(); + + // makes a move at the given position. + void MakeHumanPlayerMove(int row, int column); + + // event handlers. + void OnSquareClicked(POINTS point); + void OnSquareActivated(UINT regionId); + void OnNewGameClicked(); + + static const Gdiplus::Color BackgroundGazeInteractionFunctionalColor; + static const Gdiplus::Color BackgroundMouseOnlyColor; + static const Gdiplus::Color GridColor; + static const Gdiplus::Color MarkerColor; + static const Gdiplus::Color HighlightColor; + static const Gdiplus::Color WonMessageColor; + static const Gdiplus::Color LostMessageColor; + static const int SquareSize; + static const int MarkerMargin; + static const int MarkerDiameter; + static const TCHAR* WindowClassName; + + Board& _board; + EyeXHost _eyeXHost; + HINSTANCE _hInstance; + HWND _hWnd; + HMENU _hMenu; + int _leftMargin; + int _upperMargin; + + // private copy constructor and operator making the class non-copyable (declared but not implemented). + BoardWindow(const BoardWindow&); + BoardWindow& operator = (const BoardWindow&); +}; diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/ActivatableBoardGame.lastbuildstate b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/ActivatableBoardGame.lastbuildstate new file mode 100755 index 0000000..5cd4c3b --- /dev/null +++ b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/ActivatableBoardGame.lastbuildstate @@ -0,0 +1,2 @@ +#TargetFrameworkVersion=v4.0:PlatformToolSet=v140:EnableManagedIncrementalBuild=false:VCToolArchitecture=Native32Bit:WindowsTargetPlatformVersion=8.1 +Debug|Win32|D:\dev\tobii\samples\| diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/CL.command.1.tlog b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/CL.command.1.tlog new file mode 100755 index 0000000..001d8ba Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/CL.command.1.tlog differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/CL.read.1.tlog b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/CL.read.1.tlog new file mode 100755 index 0000000..ff00e89 Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/CL.read.1.tlog differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/CL.write.1.tlog b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/CL.write.1.tlog new file mode 100755 index 0000000..f3a1acb Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/CL.write.1.tlog differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/link.command.1.tlog b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/link.command.1.tlog new file mode 100755 index 0000000..5f8e2da Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/link.command.1.tlog differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/link.read.1.tlog b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/link.read.1.tlog new file mode 100755 index 0000000..de1b91d Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/link.read.1.tlog differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/link.write.1.tlog b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/link.write.1.tlog new file mode 100755 index 0000000..97ed8cf Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/link.write.1.tlog differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/rc.command.1.tlog b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/rc.command.1.tlog new file mode 100755 index 0000000..57e38ff Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/rc.command.1.tlog differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/rc.read.1.tlog b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/rc.read.1.tlog new file mode 100755 index 0000000..7e1acc5 Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/rc.read.1.tlog differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/rc.write.1.tlog b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/rc.write.1.tlog new file mode 100755 index 0000000..6c4a07a Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Activata.2732E876.tlog/rc.write.1.tlog differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/ActivatableBoardGame.log b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/ActivatableBoardGame.log new file mode 100755 index 0000000..8d71d0b --- /dev/null +++ b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/ActivatableBoardGame.log @@ -0,0 +1,8 @@ + stdafx.cpp + EyeXHost.cpp +d:\dev\tobii\samples\activatableboardgame\eyexhost.cpp(243): warning C4477: 'sprintf_s' : format string '%d' requires an argument of type 'int', but variadic argument 1 has type 'HWND' + ActivatableBoardGame.cpp + BoardWindow.cpp + Board.cpp + Generating Code... + ActivatableBoardGame.vcxproj -> D:\dev\tobii\samples\Debug\ActivatableBoardGame.exe diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/ActivatableBoardGame.obj b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/ActivatableBoardGame.obj new file mode 100755 index 0000000..cdf81f4 Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/ActivatableBoardGame.obj differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/ActivatableBoardGame.pch b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/ActivatableBoardGame.pch new file mode 100755 index 0000000..d560dcc Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/ActivatableBoardGame.pch differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/ActivatableBoardGame.res b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/ActivatableBoardGame.res new file mode 100755 index 0000000..6aae112 Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/ActivatableBoardGame.res differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Board.obj b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Board.obj new file mode 100755 index 0000000..32b7253 Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/Board.obj differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/BoardWindow.obj b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/BoardWindow.obj new file mode 100755 index 0000000..15d39e6 Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/BoardWindow.obj differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/EyeXHost.obj b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/EyeXHost.obj new file mode 100755 index 0000000..69a81a1 Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/EyeXHost.obj differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/stdafx.obj b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/stdafx.obj new file mode 100755 index 0000000..cb850a6 Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/stdafx.obj differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/vc140.idb b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/vc140.idb new file mode 100755 index 0000000..46ca7f9 Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/vc140.idb differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Debug/vc140.pdb b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/vc140.pdb new file mode 100755 index 0000000..8d46bc5 Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/Debug/vc140.pdb differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/EyeXHost.cpp b/Tobii-EyeX/samples/ActivatableBoardGame/EyeXHost.cpp new file mode 100755 index 0000000..410a6cf --- /dev/null +++ b/Tobii-EyeX/samples/ActivatableBoardGame/EyeXHost.cpp @@ -0,0 +1,377 @@ +/* + * ActivatableBoardGame sample: + * This is an example that demonstrates the use of Activatable interactors in the context of a board game. + * + * Copyright 2013-2014 Tobii Technology AB. All rights reserved. + */ + +#include "stdafx.h" +#include "EyeXHost.h" +#include +#include +#include +#include + +#pragma comment (lib, "Tobii.EyeX.Client.lib") + +#if INTPTR_MAX == INT64_MAX +#define WINDOW_HANDLE_FORMAT "%lld" +#else +#define WINDOW_HANDLE_FORMAT "%d" +#endif + +EyeXHost::EyeXHost() + : _state(Initializing), + _hWnd(nullptr), + _statusChangedMessage(0), _focusedRegionChangedMessage(0), _regionActivatedMessage(0), + _focusedRegionId(-1), + _context(TX_EMPTY_HANDLE), + _connectionStateChangedTicket(0), _queryHandlerTicket(0), _eventHandlerTicket(0) +{ + // initialize the EyeX Engine client library. + txInitializeEyeX(TX_EYEXCOMPONENTOVERRIDEFLAG_NONE, nullptr, nullptr, nullptr, nullptr); + + // create a context and register event handlers, but don't enable the connection to the engine just yet. + // we'll enable the connection in the Init method, when we're ready to handle the + // connection-status-changed notifications. + bool success = txCreateContext(&_context, TX_FALSE) == TX_RESULT_OK; + success &= RegisterConnectionStateChangedHandler(); + success &= RegisterQueryHandler(); + success &= RegisterEventHandler(); + + if (!success) + { + SetState(Failed); + } +} + +EyeXHost::~EyeXHost() +{ + if (_context != TX_EMPTY_HANDLE) + { + // shut down, then release the context. + txShutdownContext(_context, TX_CLEANUPTIMEOUT_DEFAULT, TX_FALSE); + txReleaseContext(&_context); + } +} + +void EyeXHost::Init(HWND hWnd, UINT statusChangedMessage, UINT focusedRegionChangedMessage, UINT regionActivatedMessage) +{ + _hWnd = hWnd; + _statusChangedMessage = statusChangedMessage; + _focusedRegionChangedMessage = focusedRegionChangedMessage; + _regionActivatedMessage = regionActivatedMessage; + + // connect to the engine. + if (txEnableConnection(_context) != TX_RESULT_OK) + { + SetState(Failed); + } +} + +void EyeXHost::SetActivatableRegions(const std::vector& regions) +{ + std::lock_guard lock(_mutex); + + _regions.assign(regions.begin(), regions.end()); + ResetFocusedRegionIdIfNonExistent(); +} + +int EyeXHost::GetFocusedRegionId() const +{ + std::lock_guard lock(const_cast(_mutex)); + + return _focusedRegionId; +} + +void EyeXHost::TriggerActivation() +{ + TX_HANDLE command(TX_EMPTY_HANDLE); + txCreateActionCommand(_context, &command, TX_ACTIONTYPE_ACTIVATE); + txExecuteCommandAsync(command, NULL, NULL); + txReleaseObject(&command); +} + +void EyeXHost::TriggerActivationModeOn() +{ + TX_HANDLE command(TX_EMPTY_HANDLE); + txCreateActionCommand(_context, &command, TX_ACTIONTYPE_ACTIVATIONMODEON); + txExecuteCommandAsync(command, NULL, NULL); + txReleaseObject(&command); +} + +void EyeXHost::SetFocusedRegionId(int regionId) +{ + std::lock_guard lock(_mutex); + + _focusedRegionId = regionId; + ResetFocusedRegionIdIfNonExistent(); + PostMessage(_hWnd, _focusedRegionChangedMessage, 0, 0); +} + +// this method assumes that the mutex is held during the call. +void EyeXHost::ResetFocusedRegionIdIfNonExistent() +{ + for (auto region : _regions) + { + if (region.id == _focusedRegionId) + { + return; + } + } + + _focusedRegionId = -1; +} + +bool EyeXHost::IsFunctional() const +{ + return _state == Initializing || + _state == Connected; +} + +void EyeXHost::SetState(State state) +{ + std::lock_guard lock(_mutex); + + if (_state != state) + { + _state = state; + + // note the use of the asynchronous PostMessage function to marshal the event to the main thread. + // (this method is called from OnEngineConnectionStateChanged, which is typically invoked on a worker thread.) + PostMessage(_hWnd, _statusChangedMessage, 0, 0); + } +} + +bool EyeXHost::RegisterConnectionStateChangedHandler() +{ + // we pass the "this" pointer as the user parameter when registering the event handler, + // so that we can access it in the callback function. + + auto connectionStateChangedTrampoline = [](TX_CONNECTIONSTATE connectionState, TX_USERPARAM userParam) + { + static_cast(userParam)->OnEngineConnectionStateChanged(connectionState); + }; + + bool success = txRegisterConnectionStateChangedHandler( + _context, + &_connectionStateChangedTicket, + connectionStateChangedTrampoline, + this) == TX_RESULT_OK; + + return success; +} + +bool EyeXHost::RegisterQueryHandler() +{ + auto queryHandlerTrampoline = [](TX_CONSTHANDLE hObject, TX_USERPARAM userParam) + { + static_cast(userParam)->HandleQuery(hObject); + }; + + bool success = txRegisterQueryHandler( + _context, + &_queryHandlerTicket, + queryHandlerTrampoline, + this) == TX_RESULT_OK; + + return success; +} + +bool EyeXHost::RegisterEventHandler() +{ + auto eventHandlerTrampoline = [](TX_CONSTHANDLE hObject, TX_USERPARAM userParam) + { + static_cast(userParam)->HandleEvent(hObject); + }; + + bool success = txRegisterEventHandler(_context, + &_eventHandlerTicket, + eventHandlerTrampoline, + this) == TX_RESULT_OK; + + return success; +} + +void EyeXHost::OnEngineConnectionStateChanged(TX_CONNECTIONSTATE connectionState) +{ + switch (connectionState) + { + case TX_CONNECTIONSTATE::TX_CONNECTIONSTATE_CONNECTED: + SetState(Connected); + break; + + case TX_CONNECTIONSTATE::TX_CONNECTIONSTATE_DISCONNECTED: + case TX_CONNECTIONSTATE::TX_CONNECTIONSTATE_TRYINGTOCONNECT: + case TX_CONNECTIONSTATE::TX_CONNECTIONSTATE_SERVERVERSIONTOOLOW: + case TX_CONNECTIONSTATE::TX_CONNECTIONSTATE_SERVERVERSIONTOOHIGH: + SetState(Disconnected); + break; + + default: + break; + } +} + +void EyeXHost::HandleQuery(TX_CONSTHANDLE hAsyncData) +{ + std::lock_guard lock(_mutex); + + // NOTE. This method will fail silently if, for example, the connection is lost before the snapshot has been committed, + // or if we run out of memory. This is by design, because there is nothing we can do to recover from these errors anyway. + + TX_HANDLE hQuery(TX_EMPTY_HANDLE); + txGetAsyncDataContent(hAsyncData, &hQuery); + + const int bufferSize = 20; + TX_CHAR stringBuffer[bufferSize]; + + // read the query bounds from the query, that is, the area on the screen that the query concerns. + // the query region is always rectangular. + TX_HANDLE hBounds(TX_EMPTY_HANDLE); + txGetQueryBounds(hQuery, &hBounds); + TX_REAL pX, pY, pWidth, pHeight; + txGetRectangularBoundsData(hBounds, &pX, &pY, &pWidth, &pHeight); + txReleaseObject(&hBounds); + Gdiplus::Rect queryBounds((INT)pX, (INT)pY, (INT)pWidth, (INT)pHeight); + + // create a new snapshot with the same window id and bounds as the query. + TX_HANDLE hSnapshot(TX_EMPTY_HANDLE); + txCreateSnapshotForQuery(hQuery, &hSnapshot); + + TX_CHAR windowIdString[bufferSize]; + sprintf_s(windowIdString, bufferSize, WINDOW_HANDLE_FORMAT, _hWnd); + + if (QueryIsForWindowId(hQuery, windowIdString)) + { + // define options for our activatable regions: no, we don't want tentative focus events. + TX_ACTIVATABLEPARAMS params = { TX_FALSE }; + + // iterate through all regions and create interactors for those that overlap with the query bounds. + for (auto region : _regions) + { + Gdiplus::Rect regionBounds((INT)region.bounds.left, (INT)region.bounds.top, + (INT)(region.bounds.right - region.bounds.left), (INT)(region.bounds.bottom - region.bounds.top)); + + if (queryBounds.IntersectsWith(regionBounds)) + { + TX_HANDLE hInteractor(TX_EMPTY_HANDLE); + sprintf_s(stringBuffer, bufferSize, "%d", region.id); + + TX_RECT bounds; + bounds.X = region.bounds.left; + bounds.Y = region.bounds.top; + bounds.Width = region.bounds.right - region.bounds.left; + bounds.Height = region.bounds.bottom - region.bounds.top; + + txCreateRectangularInteractor(hSnapshot, &hInteractor, stringBuffer, &bounds, TX_LITERAL_ROOTID, windowIdString); + txCreateActivatableBehavior(hInteractor, ¶ms); + + txReleaseObject(&hInteractor); + } + } + } + + txCommitSnapshotAsync(hSnapshot, OnSnapshotCommitted, nullptr); + txReleaseObject(&hSnapshot); + txReleaseObject(&hQuery); +} + +void EyeXHost::HandleEvent(TX_CONSTHANDLE hAsyncData) +{ + TX_HANDLE hEvent(TX_EMPTY_HANDLE); + txGetAsyncDataContent(hAsyncData, &hEvent); + + // NOTE. Uncomment the following line of code to view the event object. The same function can be used with any interaction object. + //OutputDebugStringA(txDebugObject(hEvent)); + + // read the interactor ID from the event. + const int bufferSize = 20; + TX_CHAR stringBuffer[bufferSize]; + TX_SIZE idLength(bufferSize); + if (txGetEventInteractorId(hEvent, stringBuffer, &idLength) == TX_RESULT_OK) + { + int interactorId = atoi(stringBuffer); + + HandleActivatableEvent(hEvent, interactorId); + } + + txReleaseObject(&hEvent); +} + +void EyeXHost::HandleActivatableEvent(TX_HANDLE hEvent, int interactorId) +{ + TX_HANDLE hActivatable(TX_EMPTY_HANDLE); + if (txGetEventBehavior(hEvent, &hActivatable, TX_BEHAVIORTYPE_ACTIVATABLE) == TX_RESULT_OK) + { + TX_ACTIVATABLEEVENTTYPE eventType; + if (txGetActivatableEventType(hActivatable, &eventType) == TX_RESULT_OK) + { + if (eventType == TX_ACTIVATABLEEVENTTYPE_ACTIVATED) + { + OnActivated(hActivatable, interactorId); + } + else if (eventType == TX_ACTIVATABLEEVENTTYPE_ACTIVATIONFOCUSCHANGED) + { + OnActivationFocusChanged(hActivatable, interactorId); + } + } + + txReleaseObject(&hActivatable); + } +} + +void EyeXHost::OnActivationFocusChanged(TX_HANDLE hBehavior, int interactorId) +{ + TX_ACTIVATIONFOCUSCHANGEDEVENTPARAMS eventData; + if (txGetActivationFocusChangedEventParams(hBehavior, &eventData) == TX_RESULT_OK) + { + if (eventData.HasActivationFocus) + { + SetFocusedRegionId(interactorId); + } + else + { + SetFocusedRegionId(-1); + } + } +} + +void EyeXHost::OnActivated(TX_HANDLE hBehavior, int interactorId) +{ + PostMessage(_hWnd, _regionActivatedMessage, interactorId, 0); +} + +void TX_CALLCONVENTION EyeXHost::OnSnapshotCommitted(TX_CONSTHANDLE hAsyncData, TX_USERPARAM param) +{ + // check the result code using an assertion. + // this will catch validation errors and runtime errors in debug builds. in release builds it won't do anything. + + TX_RESULT result = TX_RESULT_UNKNOWN; + txGetAsyncDataResultCode(hAsyncData, &result); + assert(result == TX_RESULT_OK || result == TX_RESULT_CANCELLED); +} + +bool EyeXHost::QueryIsForWindowId(TX_HANDLE hQuery, const TX_CHAR* windowId) +{ + const int bufferSize = 20; + TX_CHAR buffer[bufferSize]; + + TX_SIZE count; + if (TX_RESULT_OK == txGetQueryWindowIdCount(hQuery, &count)) + { + for (int i = 0; i < count; i++) + { + TX_SIZE size = bufferSize; + if (TX_RESULT_OK == txGetQueryWindowId(hQuery, i, buffer, &size)) + { + if (0 == strcmp(windowId, buffer)) + { + return true; + } + } + } + } + + return false; +} diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/EyeXHost.h b/Tobii-EyeX/samples/ActivatableBoardGame/EyeXHost.h new file mode 100755 index 0000000..98aa84d --- /dev/null +++ b/Tobii-EyeX/samples/ActivatableBoardGame/EyeXHost.h @@ -0,0 +1,111 @@ +/* + * EyeXHost class: Responsible for the gaze interaction within a window. + * Holds the current set of activatable regions and acts as a simple interactor repository. + * Sends notifications as Windows messages so that they are received on the main thread and can be handled there. + * + * Copyright 2013 Tobii Technology AB. All rights reserved. + */ + +#pragma once + +#include +#include +#include +#include "eyex/EyeX.h" + +class EyeXHost +{ +public: + // Represents an activatable region, that is, one particular kind of interactor. + struct ActivatableRegion + { + int id; + RECT bounds; + + ActivatableRegion(int paramId, RECT paramBounds) : id(paramId), bounds(paramBounds) { } + }; + + EyeXHost(); + virtual ~EyeXHost(); + + // attaches to the window with the given handle. + // the message parameters are custom windows messages sent to the window when an event has occurred. + void Init(HWND hWnd, UINT statusChangedMessage, UINT focusedRegionChangedMessage, UINT regionActivatedMessage); + + // updates the collection (repository) of activatable regions. + void SetActivatableRegions(const std::vector& regions); + + // gets the ID of the region that currently has the activation focus. + int GetFocusedRegionId() const; + + // triggers an activation ("direct click"). + void TriggerActivation(); + + // trigger a request to the EyeX Engine to switch activation mode on. + // this will trigger Activation Focus Changed events to be raised for the + // interactor the user is looking at/no longer looking at. + // in this sample, the focus is used to draw a highlight on in the square + // that is about to be "clicked" - where the "X" will be placed. + void TriggerActivationModeOn(); + + // indicates whether the connection to the EyeX Engine is working. + bool IsFunctional() const; + +private: + enum State + { + Initializing, + Connected, + Disconnected, + Failed + }; + + // registers handlers for notifications from the engine. + bool RegisterConnectionStateChangedHandler(); + bool RegisterQueryHandler(); + bool RegisterEventHandler(); + + // event handlers. + void OnEngineConnectionStateChanged(TX_CONNECTIONSTATE connectionState); + void HandleQuery(TX_CONSTHANDLE hAsyncData); + void HandleEvent(TX_CONSTHANDLE hAsyncData); + void HandleActivatableEvent(TX_HANDLE hEvent, int interactorId); + void OnActivationFocusChanged(TX_HANDLE hBehavior, int interactorId); + void OnActivated(TX_HANDLE hBehavior, int interactorId); + + // callback function invoked when a snapshot has been committed. + static void TX_CALLCONVENTION OnSnapshotCommitted(TX_CONSTHANDLE hAsyncData, TX_USERPARAM param); + + // sets the internal state. + void SetState(State state); + + // sets the ID of the region that currently has the activation focus. + void SetFocusedRegionId(int regionId); + + // clears the focused region ID if there is no matching region in the repository. + void ResetFocusedRegionIdIfNonExistent(); + + static bool QueryIsForWindowId(TX_HANDLE hQuery, const TX_CHAR* windowId); + + // mutex protecting the state of the object from race conditions caused by multiple threads. + // (for example, a call to SetActivatableRegions from the main thread while the HandleQuery + // method is iterating through the regions on a worker thread.) + std::mutex _mutex; + State _state; + std::vector _regions; + int _focusedRegionId; + TX_CONTEXTHANDLE _context; + TX_TICKET _connectionStateChangedTicket; + TX_TICKET _queryHandlerTicket; + TX_TICKET _eventHandlerTicket; + + // attached window and custom messages. + HWND _hWnd; + UINT _statusChangedMessage; + UINT _focusedRegionChangedMessage; + UINT _regionActivatedMessage; + + // private copy constructor and operator making the class non-copyable (declared but not implemented). + EyeXHost(const EyeXHost&); + EyeXHost& operator = (const EyeXHost&); +}; diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Observer.h b/Tobii-EyeX/samples/ActivatableBoardGame/Observer.h new file mode 100755 index 0000000..8aa98c1 --- /dev/null +++ b/Tobii-EyeX/samples/ActivatableBoardGame/Observer.h @@ -0,0 +1,13 @@ +/* + * Observer interface: Standard Observer interface used for notifying an object of a change in another object. + * + * Copyright 2013 Tobii Technology AB. All rights reserved. + */ + +#pragma once + +class Observer +{ +public: + virtual void SubjectChanged() = 0; +}; diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/Resource.h b/Tobii-EyeX/samples/ActivatableBoardGame/Resource.h new file mode 100755 index 0000000..6a23727 Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/Resource.h differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/small.ico b/Tobii-EyeX/samples/ActivatableBoardGame/small.ico new file mode 100755 index 0000000..449296f Binary files /dev/null and b/Tobii-EyeX/samples/ActivatableBoardGame/small.ico differ diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/stdafx.cpp b/Tobii-EyeX/samples/ActivatableBoardGame/stdafx.cpp new file mode 100755 index 0000000..92e931f --- /dev/null +++ b/Tobii-EyeX/samples/ActivatableBoardGame/stdafx.cpp @@ -0,0 +1,3 @@ +// stdafx.cpp : source file that includes just the standard includes + +#include "stdafx.h" diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/stdafx.h b/Tobii-EyeX/samples/ActivatableBoardGame/stdafx.h new file mode 100755 index 0000000..705c92c --- /dev/null +++ b/Tobii-EyeX/samples/ActivatableBoardGame/stdafx.h @@ -0,0 +1,17 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently + +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files: +#include + +// C RunTime Header Files +#include +#include +#include +#include diff --git a/Tobii-EyeX/samples/ActivatableBoardGame/targetver.h b/Tobii-EyeX/samples/ActivatableBoardGame/targetver.h new file mode 100755 index 0000000..90e767b --- /dev/null +++ b/Tobii-EyeX/samples/ActivatableBoardGame/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include -- cgit v1.2.3