Skip to content

Commit 5443b32

Browse files
committed
Add chessboard example
Note: description need to be revised
1 parent a3f25be commit 5443b32

File tree

13 files changed

+470
-1
lines changed

13 files changed

+470
-1
lines changed

ch16/graphical_user_interfaces.tex

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
% !TEX encoding = UTF8
2+
% !TEX spellcheck = ru_RU
3+
% !TEX root = ../seminars.tex
4+
5+
%%===============================================
6+
\chapter{Графические пользовательские интерфейсы}
7+
%%===============================================
8+
9+
%%====================================
10+
\section{Окно с кнопкой \texttt{Quit}}
11+
%%====================================
12+
Выполним упражнение~1 из~\textbookref{главы~16} учебника. Для~этого пронаследуем \code{My\_window} от~класса \code{Simple\_window} из~библиотеки \code{Graph\_lib}. Затем добавим по~аналогии с~кнопкой \code{Next} кнопку \code{Quit}, как показано на~рисунке~\ref{fig:mywindow}.
13+
14+
\begin{figure}[ht]
15+
{\centering
16+
\includegraphics[width=0.6\textwidth]{images/my_window.png}
17+
18+
}
19+
\caption{Простое окно \code{My\_window} с кнопкой \code{Quit}}
20+
\label{fig:mywindow}
21+
\end{figure}
22+
23+
Из~документации к~библиотеке \name{FLTK} известно, что если все окна становятся скрытыми, то цикл обработки событий прекращает работу. То есть для~реализации функции \code{quit()} можно воспользоваться методом \code{hide()}.
24+
25+
\cppfile[firstline=11, lastline=30]{projects/ch16/chessboard/board.h}
26+
27+
28+
29+
%%=======================
30+
\section{Шахматная доска}
31+
%%=======================
32+
Покажем, как можно создать клеточное поле и взаимодействовать с~ним на~примере упражнения~2 из~\textbookref{главы~16}. Эти идеи можно использовать для~создания практически любой игры с~клеточным полем: шашки, шахматы, сапёр, морской бой и другие.
33+
34+
\begin{figure}[ht]
35+
{\centering
36+
\includegraphics[width=0.5\textwidth]{images/chessboard.png}
37+
38+
}
39+
\caption{Окно с шахматной доской \(4\times 4\)}
40+
\label{fig:chessboard}
41+
\end{figure}
42+
43+
Размеры клеток и, соответственно, окна зафиксируем. Впоследствии, такое поведение можно изменить. Клетки представим квадратными кнопками и будем хранить их в~\code{Vector\_ref}. Метки строк и столбцов доски можно нарисовать при~помощи объектов \code{Marks}. Окно, согласно заданию, пронаследуем от~\code{My\_window} из~предыдущего упражнения.
44+
45+
\cppfile[firstline=32, lastline=60]{projects/ch16/chessboard/board.h}
46+
47+
Учитывая последующую доработку, код удобнее распределить между~несколькими файлами, как это предлагается в~таблице~\ref{tab:chessboard}.
48+
\begin{table}[ht]
49+
{\centering\begin{tabular}{ll}
50+
\toprule
51+
\code{board.h} & класс \code{Chessboard} для~шахматной доски, а также \code{My\_window} \\
52+
\code{board.cpp} & \\[0.5em]
53+
54+
\code{cell.h} & класс \code{Cell} для~шахматной клетки \\
55+
\code{cell.cpp} & \\[0.5em]
56+
57+
\code{main.cpp} & \\
58+
\bottomrule
59+
\end{tabular}
60+
61+
}
62+
\medskip
63+
\caption{Распределение кода <<шахматной доски>> между файлами}
64+
\label{tab:chessboard}
65+
\end{table}
66+
67+
Конструктор задаёт размеры окна, создаёт клетки, располагая их в~соответствующих позициях, и рисует метки строк и столбцов доски. Зафиксировать размеры окна, чтобы пользователь не~смог его растягивать или сжимать, позволяет функция \code{size\_range()} "--- метод окна \name{FLTK}.
68+
69+
\cppfile[firstline=35, lastline=66]{projects/ch16/chessboard/board.cpp}
70+
71+
Цвет клетки (или тип) вычисляется на~основе номеров строки и столбца, определяющих её положение на~доске. Клетка в~левом нижнем углу имеет чёрный цвет. Обратим внимание, что клетки добавлялись в~линейный массив по~порядку в~соответствии с~направлением снизу вверх и слева направо, как принято в~шахматах.
72+
73+
\cppfile[firstline=7, lastline=13]{projects/ch16/chessboard/board.cpp}
74+
75+
Метки \code{Marks} допускают всего лишь один символ, поэтому мы используем ограничение \code{N\_max} для~максимального размера доски. Если потребуется вывести двузначные номера клеток, придётся разработать новый класс или добавить метки при~помощи неименованных объектов \code{Text}. А пока используем простую реализацию и пару вспомогательных функций:
76+
77+
\cppfile[firstline=15, lastline=33]{projects/ch16/chessboard/board.cpp}
78+
79+
\noindent Таким образом, получается вид, как на~рисунке~\ref{fig:chessboard}.
80+
81+
В~функцию-обработчик нажатия на~кнопку-клетку добавим простую логику самой шахматной доски. Мы можем выделить любую клетку; переместить выделение, выбрав другую клетку; или снять выделение, нажав на~выделенную клетку повторно:
82+
83+
\cppfile[firstline=68, lastline=90]{projects/ch16/chessboard/board.cpp}
84+
85+
\noindent\textbf{NB!} Если в~отображении виджета что-то изменилось, то, скорее всего, потребуется перерисовать его вручную. Для~этого используйте \code{Fl::redraw()}.
86+
87+
Теперь посмотрим реализацию клетки шахматной доски:
88+
89+
\cppfile[firstline=8, lastline=32]{projects/ch16/chessboard/cell.h}
90+
91+
Реализация методов проста и не~требует дополнительных пояснений, кроме того, что \code{pw} "--- это защищённый член класса \code{Graph\_lib::Widget}, связанный с~реальным графическим виджетом из~\name{FLTK}.
92+
\enlargethispage{0.5\baselineskip}
93+
94+
\cppfile[firstline=5, lastline=32]{projects/ch16/chessboard/cell.cpp}
95+
96+
Теперь настало время собрать и запустить программу целиком. Для~этого добавьте необходимые заголовки и функцию \code{main()}, просто создающую окно \code{Checkerboard} и запускающую цикл обработки событий:
97+
98+
\cppfile[firstline=17, lastline=18]{projects/ch16/chessboard/main.cpp}
99+
100+
101+
102+
\clearpage
103+
%%===============================
104+
\section{Добавление фигур. Шашки}
105+
%%===============================
106+
107+
\begin{figure}[ht]
108+
{\centering
109+
\includegraphics[width=0.5\textwidth]{images/checkers.png}
110+
111+
}
112+
\caption{Шахматная доска \(4\times 4\) с~шашками}
113+
\label{fig:checkers}
114+
\end{figure}
115+
116+
\todo{Описание будет добавлено позже.}
117+
118+
119+
120+
%%================
121+
\WhatToReadSection
122+
%%================
123+
\textcite{Stroustrup:2016:ru}: \textbf{глава~17}
124+
125+
126+
127+
%%===============
128+
\ExercisesSection
129+
%%===============
130+
\begin{exercise}
131+
\item Выполните упражнения из~\textbookref{главы~16} учебника.
132+
133+
\end{exercise}

images/checkers.png

10.7 KB
Loading

images/chessboard.png

10.1 KB
Loading

images/my_window.png

5.78 KB
Loading

projects/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,5 @@ add_subdirectory(ch13/hexagon_tile)
2525
add_subdirectory(ch14/logic_shapes)
2626

2727
add_subdirectory(ch15/least_squares)
28+
29+
add_subdirectory(ch16/chessboard)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
cmake_minimum_required(VERSION 3.20)
2+
3+
set(TARGET "chessboard")
4+
5+
set(HEADERS
6+
board.h
7+
cell.h
8+
${LIB_DIR}/Graph_lib/fltk.h
9+
${LIB_DIR}/Graph_lib/Graph.h
10+
${LIB_DIR}/Graph_lib/GUI.h
11+
${LIB_DIR}/Graph_lib/Point.h
12+
${LIB_DIR}/Graph_lib/Simple_window.h
13+
${LIB_DIR}/Graph_lib/Window.h
14+
)
15+
set(SOURCES
16+
main.cpp
17+
board.cpp
18+
cell.cpp
19+
${LIB_DIR}/Graph_lib/Graph.cpp
20+
${LIB_DIR}/Graph_lib/GUI.cpp
21+
${LIB_DIR}/Graph_lib/Window.cpp
22+
)
23+
24+
project(${TARGET} CXX)
25+
26+
set(FLTK_SKIP_FLUID True)
27+
set(FLTK_SKIP_FORMS True)
28+
29+
find_package(FLTK 1.3.8 EXACT REQUIRED)
30+
find_package(OpenGL REQUIRED)
31+
32+
include_directories(SYSTEM ${FLTK_INCLUDE_DIR})
33+
link_directories(${FLTK_INCLUDE_DIR}/../lib)
34+
35+
add_executable(${TARGET} ${HEADERS} ${SOURCES})
36+
37+
target_link_libraries(${TARGET} ${FLTK_LIBRARIES} ${OPENGL_LIBRARIES})
38+
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
39+
target_link_libraries(${TARGET} fltk_jpeg fltk_png fltk_z)
40+
endif()
41+
42+
install(TARGETS ${TARGET})

projects/ch16/chessboard/Makefile

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
TARGET = chessboard
2+
3+
LIB_DIR = ../../lib
4+
5+
SOURCES = $(wildcard ${LIB_DIR}/Graph_lib/*.cpp) \
6+
board.cpp \
7+
cell.cpp \
8+
main.cpp
9+
10+
OBJECTS = $(patsubst %.cpp,%.o,$(SOURCES))
11+
12+
13+
CXX ?= g++
14+
CXXFLAGS := -std=c++17 -pedantic -Wall -Wextra -I${LIB_DIR} \
15+
$(patsubst -I%,-isystem%,$(shell fltk-config --use-images --cxxflags))
16+
LDFLAGS := $(shell fltk-config --use-images --ldflags)
17+
18+
19+
.DEFAULT_GOAL := all
20+
.PHONY: all clean clean-all
21+
22+
23+
$(TARGET): $(OBJECTS)
24+
$(CXX) -o $@ $(OBJECTS) $(LDFLAGS)
25+
26+
%.o: %.cpp %.h
27+
$(CXX) -c $(CXXFLAGS) -o $@ $<
28+
29+
30+
all: $(TARGET)
31+
32+
33+
clean:
34+
rm -f $(OBJECTS)
35+
36+
clean-all: clean
37+
rm -f $(TARGET)
38+

projects/ch16/chessboard/board.cpp

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#include <iostream>
2+
3+
#include "board.h"
4+
5+
using namespace Graph_lib;
6+
7+
Cell::Type type_of_cell (int i, int j)
8+
{
9+
if (i % 2 == 0)
10+
return (j % 2 == 0) ? Cell::black : Cell::white;
11+
else
12+
return (j % 2 == 0) ? Cell::white : Cell::black;
13+
}
14+
15+
std::string letters ()
16+
{
17+
std::string s(Chessboard::N_max, '\0');
18+
19+
for (int i = 0; i < Chessboard::N_max; ++i)
20+
s[i] = 'a' + i;
21+
22+
return s;
23+
}
24+
25+
std::string digits ()
26+
{
27+
std::string s(Chessboard::N_max, '\0');
28+
29+
for (int i = 0; i < Chessboard::N_max; ++i)
30+
s[i] = '1' + i;
31+
32+
return s;
33+
}
34+
35+
Chessboard::Chessboard(Point xy)
36+
: My_window{xy, width, height, "Chessboard"}, x_labels{letters()},
37+
y_labels{digits()}
38+
{
39+
size_range(width, height, width, height); // fixed window size
40+
41+
// board
42+
constexpr int cw = Cell::size;
43+
44+
for (int i = 0; i < N; ++i)
45+
{
46+
for (int j = 0; j < N; ++j)
47+
{
48+
Point xy{margin + i * cw, margin + (N - 1 - j) * cw};
49+
cells.push_back(new Cell{xy, cb_clicked, type_of_cell(i, j)});
50+
attach(cells[cells.size() - 1]);
51+
}
52+
}
53+
54+
// labels
55+
constexpr Point lb{margin, margin + N * cw}; // left bottom board corner
56+
constexpr int dm = 10; // margin from cells
57+
58+
for (int i = 0; i < N; ++i)
59+
{
60+
int dl = i * cw + cw / 2; // distance from corner
61+
x_labels.add(Point{lb.x + dl, lb.y + dm});
62+
y_labels.add(Point{lb.x - dm, lb.y - dl});
63+
}
64+
attach(x_labels);
65+
attach(y_labels);
66+
}
67+
68+
void Chessboard::clicked(Cell& c)
69+
{
70+
if (!selected)
71+
{
72+
selected = &c;
73+
c.activate();
74+
}
75+
else
76+
{
77+
selected->deactivate();
78+
79+
if (selected == &c) // reset selection
80+
{
81+
selected = nullptr;
82+
}
83+
else // choose another cell
84+
{
85+
selected = &c;
86+
c.activate();
87+
}
88+
}
89+
Fl::redraw();
90+
}

projects/ch16/chessboard/board.h

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#ifndef BOARD_H
2+
#define BOARD_H
3+
4+
#include <Graph_lib/Simple_window.h>
5+
6+
#include "cell.h"
7+
8+
using Graph_lib::Address;
9+
using Graph_lib::Point;
10+
11+
struct My_window : Simple_window
12+
{
13+
My_window(Point xy, int w, int h, const std::string& title)
14+
: Simple_window{xy, w, h, title},
15+
quit_button{Point{x_max() - 70, 20}, 70, 20, "Quit", cb_quit}
16+
{
17+
attach(quit_button);
18+
}
19+
20+
Graph_lib::Button quit_button;
21+
22+
private:
23+
static void cb_quit (Address, Address widget)
24+
{
25+
auto& btn = Graph_lib::reference_to<Graph_lib::Button>(widget);
26+
dynamic_cast<My_window&>(btn.window()).quit();
27+
}
28+
29+
void quit () { hide(); }
30+
};
31+
32+
struct Chessboard : My_window
33+
{
34+
static constexpr int N = 4; // board N by N
35+
static constexpr int N_max = 8;
36+
37+
static_assert(N <= N_max,
38+
"do not allow board larger than N_max by N_max");
39+
40+
Chessboard(Point xy);
41+
42+
private:
43+
static constexpr int margin = 30;
44+
static constexpr int width = N * Cell::size + 2 * margin + 70;
45+
static constexpr int height = N * Cell::size + 2 * margin;
46+
47+
Graph_lib::Vector_ref<Cell> cells;
48+
Graph_lib::Marks x_labels;
49+
Graph_lib::Marks y_labels;
50+
51+
Cell* selected{nullptr}; // activated cell
52+
53+
static void cb_clicked (Address, Address widget)
54+
{
55+
auto& btn = Graph_lib::reference_to<Cell>(widget);
56+
dynamic_cast<Chessboard&>(btn.window()).clicked(btn);
57+
}
58+
59+
void clicked (Cell& c);
60+
};
61+
62+
#endif // #ifndef BOARD_H

0 commit comments

Comments
 (0)