
3. Комп’ютерна модель гри «морський бій»
3.1 Обґрунтування вибору середовища програмної реалізації
Програма реалізована в середовищі розробки Microsoft Visual Studio 2008 на мові об'єктно-орієнтованого програмування С++.
Microsoft Visual Studio 2008 допомагає індивідуальним програмістам і невеликим групам, що створюють будь-які види ПО, прискорити розробку додатків і створення призначених для користувача інтерфейсів з принципово новим рівнем зручності, підвищити ефективність колективної роботи.
Visual Studio 2008 допомагає писати код швидше, підтримуючи безліч засобів і можливостей, які підвищують продуктивність праці.
Технологія Intellisense є різновидом авто завершення: як тільки ви вводите ім'я класу або об'єкту і ставите крапку, показується список доступних членів даного класу або об'єкту. Це прискорює кодування, оскільки зменшується кількість тексту, що набирає на клавіатурі.
Візуальні конструктори Visual Studio 2008 дозволяють створювати потужні і привабливі застосування, засновані на Windows Presentation Foundation, — графічній підсистемі
3.2 Програмна реалізація
З метою зручнішого проектування, налаштування, і розділення окремих виконуваних операцій, програма побудована на класах, які взаємодіють між собою. Використання класів дає покращуваною розуміння коди, а значить налаштування і подальшу модифікацію вихідної коди програми. У даній програмі чітко просліджуються групи функцій, які із-за виконуваних ними операцій варто об'єднати в класи. Проаналізувавши наочну область програми можна виділити наступні класи:
-Game
-Main
-Computer
-Shell
Клас Main – клас, який забезпечує початок гри, містить в собі вісім функцій, а саме:
WinMain - точка входу в програму, аналог main в консольних додатках. У ній відбувається ініціалізація головного вікна.
WndProc - обробник подій. У цій функції перевіряється чи натискує кнопка.
InitializeShipButtonSelect - відбувається ініціалізація кнопок - кораблів, за допомогою яких відбувається розставляння їх на полі.
ActionsForAllButtons - обробник натиснення всіх кнопок, який залежно від типа кнопки перенаправляє подію в інший метод.
ActionsForCompFieldButton - обробник натиснення кнопок-поля комп'ютера, застосовується при грі.
ActionsForFieldButtons - обробник натиснення кнопок-поля користувача, застосовується при розставлянні.
CheckeNabledButton - перевіряє на кількість кораблів, що залишилися, на кожній палубі, якщо таких вже ні переводить кнопку в неактивований стан.
ActionsForPlaceButtons - обробник кнопок-кораблів розставляння.
Клас Game – забезпечую сам процес гри, містить в собі шість функцій:
Game - конструктор, ініціалізація всіх змінних.
InitializeFields - розставляє кнопки на формі(поля гравця і комп'ютера).
PlaceCompShip - розміщення кораблів комп'ютера.
NextCompStep - здійснюємо хід комп'ютера.
MakeUserShot - перевіряємо хід гравця і повертаємо стан.
~Game – деструктор.
Клас Computer – відповідає за гру комп’ютера. Містить в собі шість функцій:
ShotAlgorythm - алгоритм стрілянини комп'ютера.
PlaceShips - алгоритм розставляння кораблів комп'ютера
CheckShot - перевірка пострілу, занесення результатів в матрицю комп'ютера
ship_is_good - перевірка на можливість розташування корабля
set_ship_with_size - завдання корабля з певним розміром
set_ships - завдання всіх кораблів.
Клас Shell – відповідає а реалізацію пострілу.
3.3Інструкція користувача
Перед початком гри необхідно:
Запустити гру «Морський бій».
Розташувати всі свої кораблі.
Почати гру, користувач завжди стріляє першим.
Гра продовжується до тих пір, доки хтось не уб’є всі кораблі супротивника.
ВИСНОВКИ
У рамках даної курсової роботи була розроблена і реалізована гра «Морський бій». Було проведено дослідження компонентів програмного середовища Visual Studio 2008, які використовувалися при створенні гри. В результаті дослідження були виявлені наступні недоліки отриманого програмного продукту:
1. Низький штучний інтелект, тобто хід комп'ютера здійснюється випадковим чином, що робить маловірогідною перемогу комп'ютера;
2. При повному потопленні корабля це ніяк не відбивається;
3. Неможливість повернення ходів назад;
4. Працездатність додатка лише в середовищі Windows;
Проте, окрім недоліків, є і достоїнства в цього програмного продукту:
Можливість ручного розставляння кораблів гравця.
Програмний продукт маловимогливий до системних ресурсів комп'ютера.
В результаті обліку всіх зроблених вище зауважень можливе поліпшення створеного програмного продукту, на яке буде потрібно мінімум змін вихідної коди програми.
перелік посилань
Страуструп, Б. Язык программирования С++. Часть 1 [Текст] / Б. Страуструп; перевод с англ. – К.: ДиаСофт, 1993. – 264 с.
Страуструп, Б. Язык программирования С++. Часть 2 [Текст] / Б. Страуструп; перевод с англ. – К.: ДиаСофт, 1993. – 296 с.
Павловська, Т. А. C/С++. Програмування на мові високого рівня / Т. А. Павлівська. – СПб.: Пітер, 2010. – 461 с.
Прата, С. Мова програмування С++. Лекції і вправи / С. Прата. М.: Вільямс, 2006. – 1184 с.
Додаток А
Вихідний текст програми
Constants.h
const int Empty = 0;
const int EmptyAndShooted = 1;
const int DeadZone = 2;
const int Hited = 3;
const int Ship = 4;
const int Killed = 5;
const int MaxCells = 20;
const int ColSize = 10;
const int FormLeft = 20;
const int FormTop = 20;
const int ButtonWidth = 20;
const int ButtonHeight = 20;
const int WidthBetweenFields = 100;
Shell.h
#pragma once
class Shell
{
private:
int _x;
int _y;
bool _isUser;
public:
int X(){return _x;}
int Y(){return _y;}
bool IsUser(){return _isUser;}
Shell(int x,int y, bool isUser){
_x = x;
_y = y;
_isUser = isUser;
}
bool IsNull(){return _x==0 && _y==0;}
~Shell(void);
};
Computer.h
#include "Shell.h"
#include <iostream>
class Computer
{
private:
int** _compField;
int** _possibleUserField;
Shell* _lastRightCell;
Shell* direction;
public:
Computer(int** userField);
Shell* ShotAlgorythm();
bool PlaceShips();
void CheckShot(Shell* shell, int state);
~Computer(void);
private:
bool ship_is_good(int size, bool is_horiz, int row_top, int col_left, int** field);
void set_ship_with_size(int size, int** field);
void set_ships();
};
Game.h
#pragma once
#include "Computer.h"
#include "Constants.h"
#include <Windows.h>
class Game
{
private:
int** _userField;
int** _compField;
int _lostUserCells;
int _lostCompCells;
Computer* _computer;
public:
int** GetUserField(){return _userField;}
Game(void);
void InitializeFields(HWND hwnd);
void PlaceCompShip(void);
Shell* NextCompStep(void);
int MakeUserShot(int x,int y);
~Game(void);
};
Shell.cpp
#include "Shell.h"
Computer.cpp
#pragma once
#include "Computer.h"
#include "Constants.h"
using namespace std;
Computer::Computer(int** compField)
{
_compField = compField;
direction=new Shell(0,0,false);
_possibleUserField = new int*[12];
for (int i=0;i<12;i++)
{
_possibleUserField[i]=new int[12];
for (int j=0;j<12;j++)
_possibleUserField[i][j]=Empty;
}
_lastRightCell = new Shell(0,0,false);
}
void Computer::CheckShot(Shell* shell, int state)
{
if (state==Ship)
{
if (!_lastRightCell->IsNull())
direction = new Shell(shell->X()-_lastRightCell->X(),shell->Y()-_lastRightCell->Y(),false);
_possibleUserField[shell->X()+1][shell->Y()+1]=Hited;
_lastRightCell=shell;
}
else
{
if (state==Killed)
{
_possibleUserField[shell->X()+1][shell->Y()+1]=Hited;
_lastRightCell = new Shell(0,0,false);
direction = new Shell(0,0,false);
}
else
{
_possibleUserField[shell->X()+1][shell->Y()+1]=EmptyAndShooted;
}
}
}
Shell* Computer::ShotAlgorythm()
{
int x;
int y;
if (_lastRightCell->IsNull())//Если мы не нашли ни одного корабля или все убиты
{
do
{
x = rand()%ColSize;
y = rand()%ColSize;
}
while (_possibleUserField[x+1][y+1]!=Empty);
}
else
{
if (direction->IsNull())
{
do
{
int dir = rand()%2;
if (dir==0)//по горизонтали
{
int dir2 = rand()%2;
y = _lastRightCell->Y();
if (dir2==0) //влево
x = _lastRightCell->X()-1;
else//вправо
x = _lastRightCell->X()+1;
}
else//по вертикали
{
int dir2 = rand()%2;
x = _lastRightCell->X();
if (dir2==0) //вверх
y = _lastRightCell->Y()-1;
else//вниз
y = _lastRightCell->Y()+1;
}
}
while (_possibleUserField[x+1][y+1]==EmptyAndShooted);
}
else
{
x=_lastRightCell->X()+direction->X();
y=_lastRightCell->Y()+direction->Y();
}
}
return new Shell(x,y,false);
}
bool Computer::PlaceShips()
{
try
{
set_ships();
}
catch(char *err_str)
{
return false;
}
return true;
}
Computer::~Computer(void)
{
}
//Private methods for setting ships
bool Computer::ship_is_good(int size, bool is_horiz, int row_top, int col_left, int** field)
{
if(is_horiz)
{
for(int i = row_top-1; i <= row_top + 1;++i)
{
for(int j = col_left - 1;j <= col_left + size; ++j)
{
if(field[i][j] == Ship) return false;
}
}
return true;
}
else//вертикальный
{
for(int i = row_top - 1; i <= row_top + size; ++i)
{
for(int j = col_left - 1; j <= col_left + 1; ++j)
{
if(field[i][j] == Ship) return false;
}
}
return true;
}
}
void Computer::set_ship_with_size(int size, int** field)
{
bool is_horiz = rand() % 2 == 0;
int row_top = 0;
int col_left = 0;
do
{
do
{
row_top = rand() % ColSize+1;
}while( !is_horiz
&& row_top > ColSize - size);
do
{
col_left = rand() % ColSize+1;
}while( is_horiz
&& col_left > ColSize - size);
}while(!ship_is_good(size, is_horiz, row_top, col_left, field));
if(is_horiz)
{
for(int j = col_left; j < col_left + size; ++j)
{
field[row_top][j] = Ship;
}
}
else//вертикальный
{
for(int i = row_top; i < row_top + size; ++i)
{
field[i][col_left] = Ship;
}
}
}
void Computer::set_ships()
{
for(int i = 0; i < 1; ++i)
{
set_ship_with_size(4, _compField);
}
for(int i = 0; i < 2; ++i)
{
set_ship_with_size(3, _compField);
}
for(int i = 0; i < 3; ++i)
{
set_ship_with_size(2, _compField);
}
for(int i = 0; i < 4; ++i)
{
set_ship_with_size(1, _compField);
}
}
Game.cpp
#include "Game.h"
Game::Game(void)
{
//Инициализируем поля игрока и компьютера пустыми клетками
_userField = new int*[12];
_compField = new int*[12];
for (int i=0;i<12;i++)
{
_userField[i]=new int[12];
_compField[i]=new int[12];
for (int j=0;j<12;j++)
{
_userField[i][j] = Empty;
_compField[i][j] = Empty;
}
}
_lostCompCells=20;
_lostUserCells=20;
_computer = new Computer(_compField);
}
int Game::MakeUserShot(int x,int y)
{
if (_compField[x][y]==EmptyAndShooted||_compField[x][y]==Hited)
return -1;
if (_compField[x][y]==Ship)
{
_lostCompCells--;
_compField[x][y]=Hited;
for (int i=x-1;i<=x+1;i++)
for (int j=y-1;j<=y+1;j++)
if (_compField[i][j]==Ship)
return Ship;
return Killed;
}
else
{
_compField[x][y]=EmptyAndShooted;
return _compField[x][y];
}
}
void Game::InitializeFields(HWND hwnd)
{
for (int pl = 0;pl<2;pl++) //поля для игрока и компьютера
{
for (int i=0;i<10;i++)
{
for (int j=0;j<10;j++)
{
int x = pl*(10*ButtonWidth + WidthBetweenFields) + FormLeft + i*ButtonWidth; //pl*(10*ButtonWidth + 50 int y = FormTop + j*ButtonHeight;
HWND button = CreateWindowEx(WS_EX_CLIENTEDGE,L"Button",L"",WS_CHILD|WS_VISIBLE,
x,y,ButtonWidth,ButtonHeight,hwnd,(HMENU)(pl*100+i*10+j),NULL,NULL);
}
}
}
}
void Game::PlaceCompShip(void)
{
while (!_computer->PlaceShips()){}
}
Shell* Game::NextCompStep(void)
{
if (_lostCompCells==0)
{
return new Shell(-10,-10,true);
}
Shell* shell = _computer->ShotAlgorythm();
int x = shell->X()+1, y = shell->Y()+1;
int state = Empty;
if (_userField[x][y]==Ship)
{
_lostUserCells--;
_userField[x][y]=Hited;
state = Killed;
for (int i=x-1;i<=x+1&&state!=Ship;i++)
{
for (int j=y-1;j<=y+1&&state!=Ship;j++)
{
if (_userField[i][j]==Ship)
{
state = Ship;
}
}
}
}
else
{
_userField[x][y]=EmptyAndShooted;
}
_computer->CheckShot(shell,state);
if (_lostUserCells==0)
return new Shell(-20,-20,false);
return shell;
}
Game::~Game(void)
{
}
Main.cpp
#pragma once
#include <iostream>
#include <Windows.h>
#include "Game.h"
#include <time.h>
using namespace std;
//constants
const char g_szClassName[] = "myWindowClass";
const int Width = 750;
const int Height = 400;
//variables
Game* game;
int** userField;
int selectedShip;
int Ships[4] = {4,3,2,1};
int lostShips = 10;
void Initialize()
{
game = new Game();
userField = game->GetUserField();
}
void ActionsForPlaceButtons(HWND hwnd,WPARAM wParam, LPARAM lParam)
{
selectedShip = wParam;
}
void ShowCompShips(HWND hwnd,int** compfield)
{
int **field = game->GetCompField();
for (int i=0;i<10;i++)
{
for (int j=0;j<10;j++)
{
if (field[i+1][j+1]!=Empty)
{
int BtnID = 100+i*10+j;
SetDlgItemText(hwnd,BtnID,L"X");
}
}
}
}
void CheckEnabledButton(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
int shipID = selectedShip%1000-1;
Ships[shipID]--;
lostShips--;
if (Ships[shipID]==0)
{
HWND horizontalButton = GetDlgItem(hwnd,1001+shipID);
HWND verticalButton = GetDlgItem(hwnd,2001+shipID);
EnableWindow(horizontalButton,false);
EnableWindow(verticalButton,false);
}
if (lostShips==0)
{
game->PlaceCompShip();
ShowCompShips(hwnd,game->GetCompField());
}
}
void ActionsForFieldButtons(HWND hwnd, WPARAM wParam, LPARAM lParam)//расстановка кораблей
{
if (lostShips==0) return;
int x = wParam/10;
int y = wParam%10;
if (userField[x][y]!=Empty)
{
MessageBox(hwnd,L"Cell is reserved!",L"Atention",MB_ICONINFORMATION);
return;
}
if (selectedShip==0)
{
MessageBox(hwnd,L"Ship wasn't selected",L"Atention",MB_ICONINFORMATION);
return;
}
int horizonalCheck;
int verticalCheck;
if (selectedShip/1000==1)
{
horizonalCheck = selectedShip%1000+2;
verticalCheck = 3;
}
else
{
horizonalCheck = 3;
verticalCheck = selectedShip%1000+2;
}
for (int i=0;i<horizonalCheck;i++)
{
if (x+i>11)//Ограничение поля
{
MessageBox(hwnd,L"Can't place ship here!",L"Atention",MB_ICONINFORMATION);
selectedShip=0;
return;
}
for (int j=0;j<verticalCheck;j++)
{
if (userField[x+i][y+j]!=Empty)
{
MessageBox(hwnd,L"Cell is reserved!",L"Atention",MB_ICONINFORMATION);
selectedShip=0;
return;
}
}
}
for (int i=0;i<selectedShip%1000;i++)
{
int btnID;
if (selectedShip/1000==1)
{
userField[x+i+1][y+1] = Ship;
btnID = (x+i)*10+y;
}
else
{
userField[x+1][y+i+1] = Ship;
btnID = x*10+y+i;
}
wchar_t ch[2];
_itow_s(selectedShip%1000,ch,10);
SetDlgItemText(hwnd,btnID,ch);
}
CheckEnabledButton(hwnd,wParam,lParam);
selectedShip=0;
}
void ActionsForCompFieldButton(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
if (lostShips!=0) return;
wParam-=100;
int x = wParam/10;
int y = wParam%10;
int state = game->MakeUserShot(x+1,y+1);
if (state==-1)
{
MessageBox(hwnd,L"This cell was used",L"Attention",MB_ICONINFORMATION);
return;
}
LPCTSTR str;
switch (state)
{
case Ship: str=L"Hit"; break;
case Killed: str=L"kill"; break;
default: str=L"Empty";break;
}
SetDlgItemText(hwnd,12345,str);
int btnID = 100+x*10+y;
switch (state)
{
case Ship: SetDlgItemText(hwnd,btnID,L"X"); break;
case Killed:SetDlgItemText(hwnd,btnID,L"X"); break;
default: SetDlgItemText(hwnd,btnID,L"*"); break;
}
Shell* sh = game->NextCompStep();
if (sh->X()==-10)//user win
{
MessageBox(hwnd,L"You win!",L"We have winner!",MB_ICONINFORMATION);
exit(0);
}
else if (sh->X()==-20)//comp win
{
MessageBox(hwnd,L"Computer win!",L"We have winner!",MB_ICONINFORMATION);
exit(0);
}
int ux = sh->X(), uy = sh->Y();
int ustate = userField[ux+1][uy+1];
switch (ustate)
{
case Hited:SetDlgItemText(hwnd,ux*10+uy,L"X");
break;
case Ship:SetDlgItemText(hwnd,ux*10+uy,L"S"); break;
default:SetDlgItemText(hwnd,ux*10+uy,L"*");
break;
}
}
void ActionsForAllButtons(HWND hwnd,WPARAM wParam, LPARAM lParam)
{
if (hwnd == INVALID_HANDLE_VALUE) return;
try
{
if (wParam/1000>0)
{
ActionsForPlaceButtons(hwnd,wParam,lParam);
}
else
{
if (wParam/100==0)
ActionsForFieldButtons(hwnd,wParam,lParam);
else if (wParam/100==1)
ActionsForCompFieldButton(hwnd,wParam,lParam);
}
}
catch(char *err_str)
{
MessageBox(hwnd,LPCWSTR(err_str),L"",MB_ICONERROR);
exit(0);
}
}
void InitializeShipButtonSelect(HWND hwnd)
{
int yPos=4;
for (int i=4;i>0;i--)
{
wchar_t chr[2];
_itow_s(i,chr,10);
int x = FormLeft+10*(ButtonWidth+1);
int y = FormTop+(ButtonHeight+10)*(yPos-i);
HWND button = CreateWindowEx(WS_EX_CLIENTEDGE,L"Button",chr,WS_CHILD|WS_VISIBLE,
x,y,ButtonWidth*i,ButtonHeight,hwnd,(HMENU)(1000+i),NULL,NULL);//корабли по горизонтали
HWND button1 = CreateWindowEx(WS_EX_CLIENTEDGE,L"Button",chr,WS_CHILD|WS_VISIBLE,
y,x,ButtonWidth,ButtonHeight*i,hwnd,(HMENU)(2000+i),NULL,NULL);//корабли по вертикали
}
CreateWindowEx(WS_EX_NOACTIVATE,L"STATIC",L"",WS_CHILD|WS_VISIBLE,240,150,60,15,hwnd,(HMENU)(12345),NULL,NULL);
}
// Step 4: the Window Procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) //Обработка событий
{
switch(msg)
{
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_COMMAND:
if (HIWORD(wParam)==BN_CLICKED)//если событие нажатия на кнопку
{
ActionsForAllButtons(hwnd,wParam,lParam);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) //main
{
srand(time(0));
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
//Step 1: Registering the Window Class
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = LPCWSTR(g_szClassName);
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if(!RegisterClassEx(&wc))
{
MessageBox(NULL, L"Window Registration Failed!", L"Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
// Step 2: Creating the Window
hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
LPCWSTR(g_szClassName),
L"Sea Battle",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, Width, Height,
NULL, NULL, hInstance, NULL);
if(hwnd == NULL)
{
MessageBox(NULL, L"Window Creation Failed!", L"Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
//Create controls
Initialize();
InitializeShipButtonSelect(hwnd);
game->InitializeLetters(hwnd);
game->InitializeFields(hwnd);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Step 3: The Message Loop
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
ДОДАТОК В
Тестування програми
Рис.1 – Початок гри «Морський бій»
Рис.2 – Гравець розташував свої кораблі
Рис.3 – Повідомлення про невірне розташування корабля
Рис.4 – Повідомлення про попадання у корабель супротивника
Рис.5 – Повідомлення про потоплення корабля супротивника
Рис.6 – Повідомлення про отримання перемоги гравцем
Рис.7 – Повідомлення про перемогу комп’ютера
Рис.8 – Перевірка вірності розташування кораблів комп’ютера