Go Moku - część 1

Powrót do spisu treści

Wymagane jest zapoznanie się z następującymi podrozdziałami:

OL031 - instalacja biblioteki SDL w środowisku Dev-C++
OL032 - inicjalizacja biblioteki SDL
OL033 - powierzchnie graficzne w SDL
OL034 - przygotowanie plików własnej biblioteki graficznej
OL035 - rysowanie po powierzchni graficznej
OL036 - algorytm Bresenhama rysowania odcinka
OL037 - obcinanie grafiki do prostokąta
OL038 - podstawowe algorytmy wypełniania obszarów
OL039 - algorytm Smitha
OL040 - praca w środowisku sterowanym zdarzeniami
OL041 - czcionka bitmapowa
OL042 - czcionka wektorowa
OL043 - przyciski poleceń
OL050 - macierze - podstawowe operacje na macierzach
OL051 - przekształcenia na płaszczyźnie
OL052 - algorytm wypełniania wielokątów
OL053 - rysowanie okręgów i kół
OL054 - rysowanie elips i owali

UWAGA!!!

Bieżące opracowanie NIE JEST KURSEM programowania biblioteki SDL. Są to materiały przeznaczone do zajęć na kole informatycznym w I LO w Tarnowie.

Przed pracą z tym rozdziałem utwórz w środowisku Dev-C++ nowy projekt SDL i dołącz do niego pliki SDL_gfx.cpp oraz SDL_gfx.h. Jeśli nie przeczytałeś zalecanych rozdziałów, koniecznie zrób to, a wszystko stanie się jasne. W przeciwnym razie odpuść sobie również ten rozdział. Opis funkcji bibliotecznych znajdziesz w podsumowaniu SDL013.

 

Artykuł w PRZEBUDOWIE


SDLGo Moku w języku japońskim oznacza "Pięć Kamieni". Jest to starożytna gra logiczna wywodząca się z Azji - bardzo popularna w Japonii i w Chinach. Jeśli zapytamy o nią początkującego w tej grze, to  odpowie nam, że jest bardzo prosta, z kolei mistrz odpowie, że jest bardzo trudna. Wszystko zależy od poziomu gracza. Gra jest bardzo "uzależniająca", lecz jednocześnie uczy logicznego myślenia oraz przewidywania i konsekwencji różnych posunięć. Biegłość osiąga się po kilkunastu miesiącach nauki, natomiast poziom mistrzowski jest dostępny tylko dla uzdolnionych graczy po kilku latach intensywnego treningu.

Zasady są następujące:

Plansza gry nosi nazwę goban. Istnieje kilka odmian plansz - my zajmiemy się planszami zbudowanymi z 15 x 15 pól lub 19 x 19 pól.

goban

Gracze wykonują ruchy na przemian posługując się okrągłymi kamyczkami w kolorze czarnym i białym. Piony stawia się na przecięciu linii siatki gobanu. Rozpoczyna gracz czarny. Grę wygrywa ten z graczy, który jako pierwszy ustawi w kolumnie, rzędzie lub po przekątnej dokładnie 5 pionów w swoim kolorze jeden obok drugiego (więcej niż 5 pionów nie oznacza zwycięstwa). Poniżej mamy przykład wygranej przez piony czarne.

partia go moku

Przebieg rozgrywki można notować zapisując kolejne posunięcia gracza czarnego i białego. Każda pozycja na planszy posiada współrzędne - literkę określającą kolumnę oraz liczbę określającą wiersz. Na przykład czarny zwykle rozpoczyna na polu h8 (środek planszy) - biały zwykle stawia po przekątnej, np. na polu i7:

otwarcie go moku

Rozgrywka gomoku
Lp. Czarny Biały
1. h8 i7
2. ... ...

Celem naszego projektu będzie napisanie programu wykorzystującego utworzoną przez nas bibliotekę graficzną SDL_gfx oraz bibliotekę SDL_gui, który obsłuży planszę do gry Go Moku. Program powinien umożliwiać:

Rysowanie planszy gry

Na początek opracujemy procedurę rysowania planszy gry. Plansza będzie budowana z "klocków" (przyjęcie takiego rozwiązania wyjaśni się później), które odpowiadają polu zajętemu przez pion. Ponieważ piony stawiamy na przecięciu linii siatki, to granice pól nie będą przebiegały przez linie siatki, lecz w środku pomiędzy nimi. Ideę tę wyjaśnia poniższy rysunek:

Przy tym podziale planszy gry zauważamy, iż składa się ona z dziewięciu rodzajów pól w zależności od ich położenia na planszy. Pola te oznaczmy kolejnymi literkami od a a do j. Spójrz na poniższy rysunek:

Pola a, c, g i j występują tylko w narożnikach planszy. Pola b w górnym wierszu (z wyjątkiem narożników), a pola d, f i h odpowiednio w lewej kolumnie, w prawej kolumnie oraz w dolnym wierszu. Reszta planszy wypełniona jest polami typu e. Narzucającym się rozwiązaniem będzie zaprojektowanie funkcji, która jako parametry przyjmie współrzędne kolumna wiersz pola planszy i narysuje odpowiednie pole we właściwym miejscu ekranu.

Określmy następujące parametry:

pfsize  -  rozmiar planszy gry. Możliwe wartości to 15 (plansza 15 x 15) lub 19 (plansza (19 x 19).
pcol  - numer kolumny, w której znajduje się pole planszy, wartości od 1 do rozmiar.
prow  - numer kolumny, w której znajduje się pole gry, wartości od 1 do rozmiar

Każde pole planszy GoMoku może zawierać cztery linie fragmentu siatki w następującym układzie:

Wszystkie linie biegną od środka pola do środka boku pola:

lg - linia górna
ld - linia dolna
ll - linia lewa
lp - linia prawa

Warunki rysowania poszczególnych linii są następujące

linia górna lg prow < pfsize
linia dolna ld prow > 1
linia lewa ll pcol > 1
linia prawa lp pcol < pfsize

Otrzymaliśmy niesamowicie proste warunki! Pozwolą nam one narysować dowolny element planszy gry w zależności od jego położenia.


Teraz określimy wymiary pól planszy oraz sposób obliczania ich współrzędnych na ekranie. Oprzemy się na wymiarach ekranu, które można odczytać bezpośrednio ze struktury SDL_Surface. Ustalmy co następuje:

pfsize  -  rozmiar planszy gry. Możliwe wartości to 15 (plansza 15 x 15) lub 19 (plansza (19 x 19).
screen  - struktura SDL_Surface
screen.w  - szerokość ekranu w pikselach - przynajmniej 800!
screen.h  - wysokość ekranu w pikselach - przynajmniej 600!
pfx  - współrzędna x lewego górnego narożnika planszy
pfy  - współrzędna y lewego górnego narożnika planszy
pwh  - szerokość lub wysokość pola gry - jest ono kwadratowe
pfwh  - szerokość lub wysokość planszy

Całą planszę gry umieścimy w lewej części okna. Wokół pól musimy zarezerwować obszar na opis współrzędnych. Jeśli do opisu zastosujemy czcionkę vecprop9x12.fnt, to paski te powinny posiadać szerokość przynajmniej 16 pikseli. Poniższy rysunek przedstawia wstępne rozplanowanie ekranu gry.

Wykonujemy następujący algorytm obliczeniowy:

K01: pfwh ← screen.h - 32 ; wyznaczamy wstępną szerokość/wysokość planszy
K02: pwh ← pfwh div pfsize ; wyznaczamy wstępną szerokość/wysokość pola gry
K03: Jeśli pwh mod 2 = 0, pwh ← pwh - 1 ; pole gry powinno powinno być szerokie/wysokie na nieparzystą ilość pikseli. Wtedy środek będzie równo odległy od jego krawędzi
K04: pfwh ← pwh • pfsize ; obliczamy rzeczywistą szerokość/wysokość planszy
K05: pfx ← (screen.h - pfwh) div 2 ; modyfikujemy współrzędne lewego górnego narożnika planszy
K06: pfy ← pfx  
K07: Koniec  

Algorytm ten wyznacza położenie planszy na ekranie, jej rozmiary oraz wielkość pola gry. Będzie on wykonywany przy każdej zmianie typu planszy. Mając te wymiary możemy teraz określić współrzędne ekranowe każdego pola gry w zależności od jego numeru wiersza i kolumny:

px ← (pcol - 1) • pwh + pfx

py ← (pfsize - prow) • pwh + pfy


Wiemy już jak wyznaczyć wielkość pola gry, jego położenie na ekranie oraz zawarte w nim linie siatki. Kolejnym problemem będzie rysowanie pól z różną zawartością. Na potrzeby naszej gry określmy następujące typy pól:

  wskazane
kursorem
nie wskazane
kursorem
pole puste
czarny pion
ostatnio postawiony
czarny pion
czarny pion w
zwycięskiej piątce
biały pion
ostatnio postawiony
biały pion
biały pion w
zwycięskiej piątce

Zatem do funkcji rysującej pole gry będziemy przekazywali dodatkowy parametr - pcode, który określa rodzaj pola. Zastosujemy kodowanie binarne:

Bity b1b0 sterują rysowaniem pionów czarnych i białych.
Bity b3 b2 sterują typem pionu.
Bit b4 steruje kolorem tła pola gry.

Mamy już wszystko, co potrzebne do utworzenia procedury rysującej planszę gry. Poniżej znajduje się program testujący rysowanie planszy gry. Nie jest to jeszcze właściwa gra. Pamiętaj, aby do katalogu projektowego przekopiować czcionkę vecprop9x12.fnt.

// I Liceum Ogólnokształcące
// w Tarnowie
// Koło informatyczne
//
// P055A - Test rysowania planszy do gry Go Moku
//----------------------------------------------

#include <SDL/SDL_gfx.h>
#include <SDL/SDL_gui.h>

// Stałe globalne
//---------------

const int SCRX = 640;          // stałe określające szerokość i wysokość
const int SCRY = 480;          // ekranu w pikselach

const int C_LOW    = 0xffcc99; // kolor normalny tła
const int C_HIGH   = 0xffffcc; // kolor podświetlonego tła

const int C_CODE   = 0x10;     // maska koloru tła
const int T_CODE   = 0xB;      // maska typu pola
const int P_CODE   = 0x3;      // maska koloru pionów

const int P_BLACK  = 1;        // kod czarnego gracza
const int P_WHITE  = 2;        // kod białego gracza

const int P_LAST   = 4;        // ostatni pion
const int P_WIN    = 8;        // pion zwycięski

// Zmienne globalne, widoczne we wszystkich funkcjach
//---------------------------------------------------

SDL_Surface * screen;
gfxFont     * font = gfxOpenFont("vecprop9x12.fnt");

Uint32 pfsize;  // rozmiar planszy w wierszach/kolumnach = 15 lub 19
Uint32 pfx,pfy; // współrzędne lewego górnego narożnika planszy
Uint32 pfwh;    // szerokość/wysokość planszy w pikselach
Uint32 pwh;     // szerokość/wysokość pola gry

// Funkcje usługowe
//-----------------

// Funkcja oblicza wszystkie wymiary nowej planszy.
// ps - liczba kolumn/wierszy na planszy = 15 lub 19
//--------------------------------------------------

void calcNewPlayfield(Uint32 ps)
{
  pfsize = ps;
  pfx = 16;
  pfwh = screen->h - 32;
  pwh = pfwh / ps;
  if(!(pwh % 2)) pwh--;
  pfwh = pwh * ps;
  pfx = pfy = (screen->h - pfwh) >> 1;
}

// Funkcja rysuje tło planszy wraz
// z opisem kolumn i wierszy
//-----------------------------------

void DrawPlayfield()
{
  if(SDL_MUSTLOCK(screen)) if(SDL_LockSurface(screen) < 0) exit(-1);
  
  SDL_Rect r;
  
  r.x = r.y = 0;
  r.w = r.h = screen->h;
  SDL_FillRect(screen, &r, C_LOW);

// rysujemy opisy kolumn na dole planszy

  char c[3]; // przechowuje tekst kolumn lub wierszy
  Uint32 x, y, i;
  
  x = pfx + (pwh >> 1); // pozycja x pierwszego znaku
  y = pfwh + pfx + ((pfx - font->h) >> 1);
  c[0] = 'a'; c[1] = 0;
  for(i = 0; i < pfsize; i++)
  {
    gfxDrawText(screen, font, c, x - (gfxTextLength(font, c) >> 1), y, 0, -1);
    c[0]++;
    x += pwh;
  }

// rysujemy opisy wierszy

  x = pfx >> 1;
  y = pfwh + pfx - (pwh >> 1) - (font->h >> 1);
  for(i = 1; i <= pfsize; i++)
  {
    c[0] = 48 + (i % 10);
    if(i > 9)
    {
      c[1] = c[0]; c[0] = '1'; c[2] = 0;
    }
    else c[1] = 0;
    gfxDrawText(screen, font, c, x - (gfxTextLength(font, c) >> 1), y, 0, -1);
    y -= pwh;
  }
      
  if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);
  
  SDL_UpdateRect(screen, 0, 0, screen->h, screen->h);
}

// Funkcja rysuje pole planszy
// pcol - numer kolumny
// prow - numer wiersza
// pcode - kod pola
//----------------------------

void DrawCell(Uint32 pcol, Uint32 prow, Uint32 pcode)
{
  if(SDL_MUSTLOCK(screen)) if(SDL_LockSurface(screen) < 0) exit(-1);
  
  SDL_Rect r;
  
// wypełniamy tło

  r.x = (pcol - 1) * pwh + pfx;
  r.y = (pfsize - prow) * pwh + pfy;
  r.w = r.h = pwh;
  SDL_FillRect(screen, &r, (pcode & C_CODE) ? C_HIGH : C_LOW);
  
// rysujemy linie siatki

  Uint32 lcx = r.x + pwh / 2;
  Uint32 lcy = r.y + pwh / 2;
  Uint32 lrx = r.x + pwh - 1;
  Uint32 lly = r.y + pwh - 1;
  Uint32 llen = 1 + (pwh >> 1);
  
  if(prow < pfsize) gfxVLine(screen, lcx, r.y, 0, llen);
  if(prow > 1)      gfxVLine(screen, lcx, lcy, 0, llen);
  if(pcol < pfsize) gfxHLine(screen, lcx, lcy, 0, llen);
  if(pcol > 1)      gfxHLine(screen, r.x, lcy, 0, llen);
    

// w 9 polach środkowych planszy rysujemy małe koła na przecięciu linii

  Uint32 fs = (pfsize + 1) >> 1;

  if(((prow == fs - 2) || (prow == fs) || (prow == fs + 2)) &&
     ((pcol == fs - 2) || (pcol == fs) || (pcol == fs + 2)))
    gfxFillCircle(screen, lcx, lcy, 2, 0);   
  
// rysujemy piona

  Uint32 pr = (llen * 3) / 4;
  
  if(pcode & P_CODE)
  {
    gfxFillCircle(screen, lcx, lcy, pr, (pcode & P_BLACK) ? 0x1f1f1f : 0xffffff);
    gfxCircle(screen, lcx, lcy, pr, 0);
    
    if(pcode & P_LAST)
    {
      Uint32 clen = 1 + (pwh >> 1);
      gfxHLine(screen, (r.x + lcx) >> 1, lcy, 0xff0000, clen);
      gfxVLine(screen, lcx, (r.y + lcy) >> 1, 0xff0000, clen);
    }
    else if(pcode & P_WIN)
    {
      pr >>= 2;
      if(pr < 3) pr = 3;
      gfxFillCircle(screen, lcx, lcy, pr, 0xff0000);
    }
  }

  if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);
  
  SDL_UpdateRect(screen, r.x, r.y, r.w, r.h);
}
 
//***********************
// *** Program główny ***
//***********************

int main(int argc, char * argv[])
{

  if(SDL_Init(SDL_INIT_VIDEO)) exit(-1);

  atexit(SDL_Quit);
  
  if(!(screen = SDL_SetVideoMode(SCRX, SCRY, 32, SDL_HWSURFACE))) exit(-1);

  SDL_WM_SetCaption("Test Go Moku",""); 

// ustalamy tryb 15 x 15

  calcNewPlayfield(15);
  
  DrawPlayfield();

// czekamy na klawisz ESC lub zamknięcie okna

  SDL_Event event;      // Unia zawierająca struktury zdarzeń
  bool running = true;  // zmienna decyzyjna


  while(running)
  {
                
// rysujemy planszę gry z przypadkowymi typami pól

    for(int i = 1; i <= pfsize; i++)
      for(int j = 1; j <= pfsize; j ++)
        if(!(rand() % 5))
          DrawCell(i, j, rand());
        else
          DrawCell(i, j, 0);
        
    SDL_WaitEvent(&event); // czekamy na zdarzenie

    switch(event.type)     // sprawdzamy, co się wydarzyło
    {
      case SDL_KEYDOWN: if(event.key.keysym.sym != SDLK_ESCAPE) break;
      case SDL_QUIT:    running = false; break;
    }
  }

// zamykamy czcionkę

  gfxCloseFont(font);
  
  return 0;

}    

Ciąg dalszy w kolejnym rozdziale.



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2017 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.