Раздел «Язык Си».OOP-KontSM:

Пример контейнера для игры в крестики-нолики

Мы будем рассматривать поле для игры в крестики-нолики на доске n x n.

Реализуем класс, позволяющий работать с таким полем. В этом классе предусмотрем создание специального объекта — итератора. Итератор позволяет иметь доступ к отдельным элементам сущностей, хранимых в контйнере так как нам будет удобно для решения конкретной задачи. Итератор — часть класса-контейнера. Но создается как ОТДЕЛЬНЫЙ объект, поэтому его нужно СВЯЗАТЬ с конкретным объектом-контейнером.

Для одного контейнера можно одновременно импользовать несколько различных иетраторов.

Заголовочный файл tictac.h

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <iostream>
#include <cstdlib>
#include <fstream>

using namespace std;

// Будем считать крестик(Х) - 1, а нолик(0) - -1
class TicTac{
  string s;
  int len; // размер поля 
  int *a;  // указатель на область памяти  для игрового поля
public:
// инициализирующий конструктор (нужен размер)
   TicTac(int);
// Деструктор для удаления выделенной памяти под поле
   ~TicTac();
// доступ к ряду поля по его номеру
   int* operator[](int);
// Класс Iteraror является классом в классе TicTac
// Функции Iterator имеют доступ ко всем атрибутам и функциям 
// класса TicTac 
   class Iterator{
      int start; // для подсчета количества итераций
      int ln;   // размер поля
      int current;
// указатели на начало и конец области памяти для поля
      int *begin, *end; 
     public:
// Конструктору нужен конкретный контейнер
       Iterator(TicTac&);
// Оператор присваивания (присваивается номер элемента в поле)
       Iterator& operator=(int);
// обход ряда поля по циклу: (0,0)->(0,1)->(0,1)->(0,0)
       Iterator& operator++(int);
// обход столбца по циклу
       Iterator& operator++();
// обход левой диагонали по циклу
       Iterator& operator--(int);
// обход правой диагонали по циклу
       Iterator& operator--();
// досуп к значению элемента поля, 
// на который указывает итератор
       int operator*();
// оператор сравнения с числом (для циклов)
       int operator<(int);      
   };    
};

Реализация tictac.cpp

#include "tictac.h"

TicTac::TicTac(int n){
// установить размер поля
   len = n;
// выделить память из кучи для поля
   a = new int[len*len];
// очистить поле.
// Но лучше использовать функцию bzero()
   for(int i=0;i<len*len;i++){
      a[i]=0;
   }
};

// деструктор
TicTac::~TicTac(){
//  дописать код для реализации деструктора
};

// оператор [] возвращает УКАЗАТЕЛЬ на начало СТРОКИ поля
// по номеру этой строки
int* TicTac::operator[](int n){
    return a + (n)*len;
};

//===== Реализация класса TicTac::Iterator =============
TicTac::Iterator::Iterator(TicTac& t){
// указатель на начало памяти для поля
     begin = t.a;
// размер поля
     ln = t.len;
// указатель на конец памяти для поля
     end = t.a + ln*ln - 1;

     start = 0;
};

// Присваивание. Присваиваем НОМЕР элемента памяти
TicTac::Iterator& TicTac::Iterator::operator=(int n){
     start= 0; // для цикла ставим 0
     current = n-1;
     return *this;
};

// Изменяем указатель на номер элемента в текущей
// строке по циклу 
TicTac::Iterator& TicTac::Iterator::operator++(int){
     int n = current / ln;
     int k = (current + 1) % ln;
     current = n * ln + k;
     start++;
     return *this; 
};

// Изменяем указатель на номер элемента в текущем
// столбце по циклу 
TicTac::Iterator& TicTac::Iterator::operator++(){
     int n = current % ln;
     int k = current / ln;
     cout<<"n="<<n<<" k="<<k<<endl;
     current = ((k+1)*ln)%(ln*ln) + n;
      cout<<"Vcur="<<current<<endl;
     start++;
     return *this; 
};

// сравниваем с числом
int TicTac::Iterator::operator<(int n ){
     if(start<n) return 1;
     return 0;     
};
// возвращаем значение элемента, на 
// указывает итератор
int TicTac::Iterator::operator*(){
    return *(begin+current);
};

Использование классов tictac.h

#include "tictac.h"

int main(){
  TicTac pole(3);
  pole[0][0] = 1;
  pole[0][1] = 2;
  pole[0][2] = 3;
  pole[1][0] = 4;
  pole[1][1] = 5;
  pole[1][2] = 6;
  pole[2][0] = 7;
  pole[2][1] = 8;
  pole[2][2] = 9;
  cout<<pole[1][1]<<endl;
  cout<<pole[2][2]<<endl;
// объявление итератора и связь его с полем
  TicTac::Iterator it(pole);
// указываем на элемет 5
  it = 5;
// печатаем значение элемента, на 
// который указывает итератор
  cout<<"ti5:"<<*it<<endl;
// обходим ряд (в которос 5 элемент) по циклу три раза
  for(it = 5; it< 3 ; ++it){
    cout<<(int)(*it)<<" ";
  }
  cout<<endl;
 }

Модернизация контейнера для совместной работы с разделяемой памятью

Для разделения доступа к ресурсам могут использоваться СЕМАФОРЫ.

Семафор в UNIX — системное устройство. Все операции, связанные с изменением сстояния семафора — АТОМАРНЫЕ. То есть, выполнение программы не может быть прервано и отложено пока операция с семафором не будет полностью завершена.

Каждый семафор имеет некоторое значение, которое при создании семафора сразу утанавливается в 0.

Над семафором можно выполнять следующие операции:

1. Увеличить значение семафора
2. Дождаться когда значение семафора станет равным 0
3. Дождаться когда значение семафора станет равным числу N

При этом понятно, что первая операция безусловная, а две последние требуют проверки значения семафора.

Рассмотрим пример работы с семафорами для игры в крестики-нолики.

Имеем два игрока, которые используют одно и тоже поле. Необходимо обеспечить очередность ходов игроков.

Мы не знаем какой из игроков первый предложит игру.

Самое первое действие, которое необходимо сделатть при организации игры — это обеспечить чтобы первый игрок дождался второго и не начинал игру один.

Для этого нужно использовать семафор номер 1

Далее доступ к полю (будем блокировать поле целиком) будем обеспечивать семафором номер 0.

Заголовочный файл для поля с семафорами tictacSM.h

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <iostream>
#include <cstdlib>
#include <fstream>

#define PERM 0666

using namespace std;


class TicTac{ 
// ключ для создания семафоров и разделяемой памяти 
  key_t key;
// описание операций с семафором (ожидание партнера)
// процесс - в ожидание
  struct sembuf waitYou[1];
// первое поле - номер семафора (1)
// второе - операция с семафором. Здесь:
// дождаться когда занчение семафора будет равно 1
// ={ 1,-1,0};

// описание операций с семафором (партнер пришел)
// партнер может действовать
  struct sembuf ImHere[1];
// первое поле - номер семафора (1)
// второе - операция с семафором. Здесь:
// увеличить значение семафора на 1 
//={ 1,1,0};

// описание операций с семафором (записает поле)
// используется 2 операции 
  struct sembuf myTurn[2];
//  Значение семафора 0 - ресурс свободен, 1 - занят
// Первая операция - дождаться когда семафор будет 0
// вторая операция - ставим семафор в 1, то есть запираем ресурс
// = {
//    0,0,0,
//    0,1,0 };

// описание операций с семафором (записает поле)
// используется 1 операция 
struct sembuf canWork[1];
//  Дождаться когда семафор будет 1 и обнулить его
// = { 0,-1,0 };

  int lng;
// очередность определяется по тому кто создал очередь семафоров
  int range; 
  char stop;
  int set;
// дескрипторы для семафоров и разделяемой памяти
  int semid,shmid;
// указатель на разделяемую память
  int *msgptr;
  string s;
  int len; // размер поля

public:
// Для ключа нужны имя файла и символ
   TicTac(string, char, int);
// необходимо удалять семафоры после работы
   ~TicTac();
   int* operator[](int);
// запирание ресурса
   void myWork();
// освобождение ресурса
   void youWork();
// получить очередность
   int getRange();
//  печать поля
   void print();
// Все операции с полем выполняются только если ресурс "захвачен"
   class Iterator{
      int start,ln;
      int current,*begin, *end;
     public:
       Iterator(TicTac&);
       Iterator& operator=(int);
       Iterator& operator++(int);
       Iterator& operator++();
       Iterator& operator--(int);
       Iterator& operator--();
       int operator*();
       int operator<(int);      
   };    
};

Реализация tictacSM.cpp

#include "tictacSM.h"

TicTac::TicTac(string s, char c, int n){

   len = n; // размер поля
// получим ключ
   if ((key=ftok(s.c_str(),c))<0){
      printf("Can't get key\n");
      exit(1);
   }
// Установим операции для семафоров:

// Захватить ресурс
// Эти операции будут выполняться сразу обе
   myTurn[0].sem_num = 0; // для семафора номер 0
   myTurn[0].sem_op = 0; //  дождаться 0
   myTurn[0].sem_flg = 0;
   myTurn[1].sem_num = 0; // для семафора номер 0
   myTurn[1].sem_op = 1; // увеличить значение на 1
   myTurn[1].sem_flg = 0;

// Освободить ресурс
   canWork[0].sem_num = 0; // для семафора номер 0
   canWork[0].sem_op = -1; //  дождаться 1 и поставить значение cемафора в 0
   canWork[0].sem_flg = 0;
 
// Ожидание партнера  
   waitYou[0].sem_num = 1; // для семафора номер 1
   waitYou[0].sem_op = -1; // дождаться 1 и поставить значение cемафора в 0
   waitYou[0].sem_flg = 0;

// Уведомление "Я пришел"
   ImHere[0].sem_num = 1; // для семафора номер 1
   ImHere[0].sem_op = 1; // увеличить значение на 1
   ImHere[0].sem_flg = 0;

// Создать разделяемую память
   shmid = shmget(key, sizeof(int)*len*len, PERM|IPC_CREAT);
   switch (errno){
      case EEXIST:{
                    if((shmid = shmget(key, sizeof(int)*len*len, 0))<0){
                   printf("Can't create shared mem\n");
                   exit(1);
                }
                  
                   break;
                 } 
      case 0:    {
                   
                   break;
                 }
      default:   { 
                   printf("Can't create shared mem\n");
                   exit(1);
                 }
  
   }
//  получить указатель на разделяемую паамять
   if ((msgptr = (int*)shmat(shmid, 0, 0))<0){
      perror(":(");
      exit(1);
   }
//  создать семафоры (2 семафора)    
   semid = semget(key,2,PERM|IPC_CREAT|IPC_EXCL);
   switch (errno){
      case EEXIST:{
                    if ((semid = semget(key, 2, PERM))<0){
                        perror(":(");
                         exit(1);
                    }
                   cout<<"Семафор уже есть\n";
                    range = 1;
                    break;
                 } 
      case 0:    {
                   range = 0;
                   break;
                 }
      default:   { 
                   printf("Can't create sema\n");
                   exit(1);
                 }
  
   }
   set = 0;
// получить свою очередь
   if(range == 0){
    cout<<"Ваш знак: 0\n";
    cout<<"Ставлю семафор на встречу в -1\n";

// semop - функция операции с семафорами
// тот, кто создал очередь семафоров ждет партнера
     if (semop(semid, &waitYou[0], 1)<0){
         printf(":(\n");
         exit(1);
     }
   }

   if(range == 1 ){
  
     cout<<"Ваш знак: X\n";
     cout<<"Чищу память\n";
     bzero(msgptr, sizeof(int)*len*len);
     cout<<"Я пришел\n";
// Уведомление "Я пришел"
     if (semop(semid, &ImHere[0], 1)<0){
      printf(":(\n");
      exit(1);
     }
   
   }
       
};
// Получить свою очередь
int TicTac::getRange(){
  return range;
};

// Удаление семафорров и разделяемой памяти
TicTac::~TicTac(){
   if (shmctl(shmid,IPC_RMID,0)<0){
      printf(":(\n");
   }

   if (semctl(semid,2,IPC_RMID)<0){
     printf(":(\n");
   }
};

void TicTac::print(){
   
  for(int j = 0;j<3;j++)
     {
      for(int i = 0;i<3;i++)
//  разделяемая память может быть использована как обычный массив
        cout << msgptr[j*3 + i]<<" ";
     
      cout<<endl;
     }
};

int* TicTac::operator[](int n){

    return msgptr + (n)*len;
};
// Запираем ресурс
void TicTac::myWork(){
   cout<<"Запираем ресурс\n";
// указываем, что выполняться будут сразу 2 операции (атомарно)
    if (semop( semid, &myTurn[0], 2)<0){
       printf(":(\n");
       exit(1);
    }
};

// освобождаем ресурс
void TicTac::youWork(){
  
    cout<<"Освобождаем ресурс\n";
    if (semop(semid, &canWork[0], 1)<0){
        printf(":(\n");
        exit(1);
   }      cout<<"Vcur="<<current<<endl;
   
};

TicTac::Iterator::Iterator(TicTac& t){
     begin = t.msgptr;
     ln = t.len;
     end = t.msgptr + ln*ln - 1;
     start = 0;
};

TicTac::Iterator& TicTac::Iterator::operator=(int n){
     start= 0;
     current = n-1;
     return *this;
};
 
TicTac::Iterator& TicTac::Iterator::operator++(int){
     int n = current / ln;
     int k = (current + 1) % ln;
     current = n * ln + k;
     cout<<"cur="<<current<<endl;
     start++;
     return *this; 
};

TicTac::Iterator& TicTac::Iterator::operator++(){
     int n = current % ln;
     int k = current / ln;
     current = ((k+1)*ln)%(ln*ln) + n;
     start++;
     return *this; 
};


int TicTac::Iterator::operator<(int n ){
     if(start < n) return 1;
     return 0;     
};
int TicTac::Iterator::operator*(){
    return *(begin+current);
};

=Использование = tic

#include "tictacSM.h"

int main(){
  TicTac pole("tic",'a',3);
  string s;
  int range = pole.getRange();

  int sgn = (range==1)?1:-1;
  int x, y;
// обеспечиваем очередность 3 ходов с каждой стороны
  for(int k= 0; k<3;k++){
     
      pole.myWork();
      pole.print();
      cout<<"Ваш ход:";
      cin>>y>>x;
     pole[y-1][x-1]=sgn;
      cout<<"ok\n";
     pole.print();
      pole.youWork();
  }
  return 0;
 }

Задачи

Задача 1.

Дописать функции итератора для класса TicTac? (c расширяемой памятью)

Задача 2.

Полностью реализовать игру в крестики нолики.

Задача 3.

В ящике летает шарик по прямой. Вектор движения шарика перпендикулярен стенкам ящика. Начальная скорость шарика - V. Положение шарика рассматривается раз в 3 секунды.

Написать две программы: первая вычисляет движения шарика и записывает из в разделяемую память, вторая - выясняет есть ли периодичность в движении шарика. Если периодичность есть, то в файл здля рассмотрения аписывается только период, если нет, все данные пока программа не закончит свою работу. Придумать и описать классы для реализации процесса.

-- TatyanaOvsyannikova2011 - 16 Nov 2016