Ермаков И. Е., Шамардина Е. Игра "Точки"
Программист — Елена Шамардина, ФСПО ТИ ОрёлГТУ
Скачать программу
Поддерживается игра по сети.
Приведение приложения к «общепринятому виду» намеренно не приводилось. Используется интерактивный текст Блэкбокса с командами, сохранен интерфейс среды. Это позволяет легко работать с многими полями игры, создавать и сохранять документы с историей игр, комментариями и т.п. И, наконец, демонстрируется, что можно совершенно «не заморачиваться».
Реализация и исходные тексты
Исходные тексты в формате ODC входят в сборку игры — подсистема Точки.
Также эта информация приведена ниже.
Подсистема Точки
Модули: ТочкиИгра, ТочкиХранение, ТочкиГрафика, ТочкиКоманды, ТочкиСетевая
В модуле ТочкиИгра реализована основная логика игры — хранение ходов, расчёт захваченных областей и их границ.
В основном используются разные варианты волнового алгоритма.
История окружений не хранится. Алгоритм каждый раз перерасчитывает области заново. Очевидно, что если удаётся построить алгоритм как «чистую функцию», то его проще реализовать и менее вероятны ошибки, которые могли бы возникнуть при модификации состояния окружений.
Единственная возникшая проблема была связана с обработкой правила «При появлении в свободной области точки соперника, свободная область будет считаться областью окружения, при условии, что точка соперника не была завершающей в его окружении». Очевидно, что для правильного расчёта таких пересекающихся потенциальных областей нужно знать, какое из окружений строить.
Таким образом, какую-то часть информации об областях нужно накапливать. Мы попытались найти такую её часть, которая после единственного присваивания уже не будет изменяться (потому что области могут разрушаться при внешнем окружении). Такой информацией является свойство каждой точки «была_в_плену». Если точка попадала в окружение, то она никогда уже не может участвовать в границе окружения. Тогда при ходе игрока в свободную область нужно начинать перерасчёт областей с того игрока, который ходил. Если ему удалось замкнуть окружение, то точка из границы противника получит признак «была_в_плену» — и при последующем расчёте его областей образовать окружение не сможет.
Модуль ТочкиИгра
MODULE ТочкиИгра;
TYPE
Игра* = POINTER TO LIMITED RECORD
поле-: Поле;
игроков-: INTEGER;
ход-: INTEGER;
статистика-: Статистика
END;
Статистика* = RECORD
количество_пленных-,
территория-: ARRAY 16 OF INTEGER
END;
Поле* = POINTER TO ARRAY OF ARRAY OF Узел;
Узел* = RECORD
точка_игрока-: INTEGER;
поставлена_на_ходе-: INTEGER;
область-: Область;
граница-: ARRAY 2 OF Область;
была_в_плену-: BOOLEAN
END;
Область* = POINTER TO RECORD
игрок-: INTEGER;
точек_противника-: INTEGER
END;
Волна = POINTER TO ARRAY OF ARRAY OF BOOLEAN;
VAR
нулевая_статистика: Статистика;
PROCEDURE Новая_игра* (ш, в: INTEGER; игроков: INTEGER): Игра;
VAR и: Игра; i, j: INTEGER;
BEGIN
ASSERT((ш >= 2) & (в >= 2), 20);
ASSERT(игроков > 0, 21);
NEW(и);
NEW(и.поле, ш, в);
и.игроков := игроков;
FOR i := 0 TO LEN(и.поле)-1 DO
FOR j := 0 TO LEN(и.поле, 1)-1 DO
и.поле[i, j].точка_игрока := -1
END
END;
RETURN и
END Новая_игра;
PROCEDURE^ Перерасчёт (и: Игра; начиная_с_игрока: INTEGER);
PROCEDURE (и: Игра) Сделать_ход* (x, y: INTEGER; игрок: INTEGER), NEW;
BEGIN
ASSERT(и.поле[x, y].точка_игрока = -1, 20);
ASSERT(игрок >= 0, 21);
и.поле[x, y].точка_игрока := игрок;
и.поле[x, y].поставлена_на_ходе := и.ход;
INC(и.ход);
Перерасчёт(и, игрок)
END Сделать_ход;
PROCEDURE^ Очистить_области (поле: Поле);
PROCEDURE^ Рассчитать_области (поле: Поле; для_игрока: INTEGER);
PROCEDURE^ Рассчитать_границы (поле: Поле; для_игрока: INTEGER);
PROCEDURE^ Посчитать_пленных (поле: Поле; для_игрока: INTEGER; OUT n: INTEGER);
PROCEDURE Посчитать_территорию (и: Игра);
VAR i, j: INTEGER;
BEGIN
FOR i := 0 TO LEN(и.поле)-1 DO
FOR j := 0 TO LEN(и.поле, 1)-1 DO
IF и.поле[i, j].область # NIL THEN
INC(и.статистика.территория[и.поле[i, j].область.игрок])
END
END
END
END Посчитать_территорию;
PROCEDURE Перерасчёт (и: Игра; начиная_с_игрока: INTEGER);
VAR i: INTEGER;
BEGIN
Очистить_области(и.поле);
и.статистика := нулевая_статистика;
i := начиная_с_игрока;
Рассчитать_области(и.поле, i);
Рассчитать_границы(и.поле, i);
Посчитать_пленных(и.поле, i, и.статистика.количество_пленных[i]);
FOR i := 0 TO и.игроков-1 DO
IF i # начиная_с_игрока THEN
Рассчитать_области(и.поле, i);
Рассчитать_границы(и.поле, i);
Посчитать_пленных(и.поле, i, и.статистика.количество_пленных[i])
END
END;
Посчитать_территорию(и)
END Перерасчёт;
PROCEDURE Очистить_области (поле: Поле);
VAR i, j: INTEGER;
BEGIN
FOR i := 0 TO LEN(поле) - 1 DO
FOR j := 0 TO LEN(поле, 1)-1 DO
поле[i, j].область := NIL;
поле[i, j].граница[0] := NIL;
поле[i, j].граница[1] := NIL;
END
END
END Очистить_области;
PROCEDURE^ Начать_волну (волна: Волна; поле: Поле; для_игрока: INTEGER);
PROCEDURE^ Ход_волны (поле: Поле; волна: Волна; для_игрока: INTEGER; OUT расширение: BOOLEAN);
PROCEDURE^ Найти_области (поле: Поле; волна: Волна; для_игрока: INTEGER);
PROCEDURE Рассчитать_области (поле: Поле; для_игрока: INTEGER);
VAR волна: POINTER TO ARRAY OF ARRAY OF BOOLEAN;
расширение: BOOLEAN;
BEGIN
NEW(волна, LEN(поле), LEN(поле, 1));
Начать_волну(волна, поле, для_игрока);
REPEAT
Ход_волны(поле, волна, для_игрока, расширение)
UNTIL ~расширение;
Найти_области (поле, волна, для_игрока)
END Рассчитать_области;
PROCEDURE Начать_волну (волна: Волна; поле: Поле; для_игрока: INTEGER);
VAR i: INTEGER;
BEGIN
FOR i := 0 TO LEN(волна)-1 DO
волна[i, 0] := поле[i, 0].точка_игрока # для_игрока;
волна[i, LEN(волна, 1)-1] := поле[i, LEN(волна, 1)-1].точка_игрока # для_игрока
END;
FOR i := 0 TO LEN(волна, 1)-1 DO
волна[0, i] := поле[0, i].точка_игрока # для_игрока;
волна[LEN(волна)-1, i] := поле[LEN(волна)-1, i].точка_игрока # для_игрока
END
END Начать_волну;
PROCEDURE Волна_рядом (волна: Волна; x, y: INTEGER): BOOLEAN;
BEGIN
RETURN волна[x, y-1] OR волна[x+1, y] OR волна[x, y+1] OR волна[x-1, y]
END Волна_рядом;
PROCEDURE Ход_волны (поле: Поле; волна: Волна; для_игрока: INTEGER; OUT расширение: BOOLEAN);
VAR i, j: INTEGER;
BEGIN
расширение := FALSE;
FOR i := 1 TO LEN(волна)-2 DO
FOR j := 1 TO LEN(волна, 1)-2 DO
IF ~волна[i, j]
& ((поле[i, j].точка_игрока # для_игрока) OR поле[i, j].была_в_плену)
& Волна_рядом(волна, i, j) THEN
волна[i, j] := TRUE;
расширение := TRUE
END
END
END
END Ход_волны;
PROCEDURE Найти_области (поле: Поле; волна: Волна; для_игрока: INTEGER);
VAR x, y: INTEGER;
PROCEDURE Найти_неопределённую;
VAR
BEGIN
x := 1;
y := 1;
WHILE (y < LEN(поле, 1)-1)
& ~( ~Волна_рядом(волна, x, y) & (поле[x, y].область = NIL) ) DO
INC(x);
IF x >= LEN(поле)-1 THEN
x := 1;
INC(y)
END
END
END Найти_неопределённую;
PROCEDURE Новая_область;
BEGIN
NEW(поле[x, y].область);
поле[x, y].область.игрок := для_игрока
END Новая_область;
PROCEDURE Заполнить_область;
VAR i, j: INTEGER; расширение: BOOLEAN;
BEGIN
REPEAT
расширение := FALSE;
FOR i := 1 TO LEN(поле)-2 DO
FOR j := 1 TO LEN(поле, 1)-2 DO
IF ~Волна_рядом(волна, i, j) & (поле[i, j].область = NIL) THEN
IF поле[i, j-1].область # NIL THEN
поле[i, j].область := поле[i, j-1].область;
расширение := TRUE
ELSIF поле[i+1, j-1].область # NIL THEN
поле[i, j].область := поле[i+1, j-1].область;
расширение := TRUE
ELSIF поле[i+1, j].область # NIL THEN
поле[i, j].область := поле[i+1, j].область;
расширение := TRUE
ELSIF поле[i+1, j+1].область # NIL THEN
поле[i, j].область := поле[i+1, j+1].область;
расширение := TRUE
ELSIF поле[i, j+1].область # NIL THEN
поле[i, j].область := поле[i, j+1].область;
расширение := TRUE
ELSIF поле[i-1, j+1].область # NIL THEN
поле[i, j].область := поле[i-1, j+1].область;
расширение := TRUE
ELSIF поле[i-1, j].область # NIL THEN
поле[i, j].область := поле[i-1, j].область;
расширение := TRUE
ELSIF поле[i-1, j-1].область # NIL THEN
поле[i, j].область := поле[i-1, j-1].область;
расширение := TRUE
END
END
END
END
UNTIL ~расширение
END Заполнить_область;
BEGIN
Найти_неопределённую;
WHILE y < LEN(поле, 1)-1 DO
Новая_область;
Заполнить_область;
Найти_неопределённую
END
END Найти_области;
PROCEDURE Добавить_границу (VAR узел: Узел; область: Область);
BEGIN
IF (узел.граница[0] # область) & (узел.граница[1] # область) THEN
IF узел.граница[0] = NIL THEN
узел.граница[0] := область
ELSE
ASSERT(узел.граница[1] = NIL, 100);
узел.граница[1] := область
END
END
END Добавить_границу;
PROCEDURE Рассчитать_границы (поле: Поле; для_игрока: INTEGER);
VAR i, j: INTEGER;
BEGIN
FOR i := 1 TO LEN(поле)-2 DO
FOR j := 1 TO LEN(поле, 1)-2 DO
IF (поле[i, j].область # NIL) & (поле[i, j].область.игрок = для_игрока) THEN
IF поле[i, j-1].область = NIL THEN
ASSERT(поле[i, j-1].точка_игрока = для_игрока, 100);
Добавить_границу(поле[i, j-1], поле[i, j].область)
END;
IF поле[i+1, j-1].область = NIL THEN
IF поле[i+1, j-1].точка_игрока = для_игрока THEN
Добавить_границу(поле[i+1, j-1], поле[i, j].область)
END
END;
IF поле[i+1, j].область = NIL THEN
ASSERT(поле[i+1, j].точка_игрока = для_игрока, 100);
Добавить_границу(поле[i+1, j], поле[i, j].область)
END;
IF поле[i+1, j+1].область = NIL THEN
IF поле[i+1, j+1].точка_игрока = для_игрока THEN
Добавить_границу(поле[i+1, j+1], поле[i, j].область)
END
END;
IF поле[i, j+1].область = NIL THEN
ASSERT(поле[i, j+1].точка_игрока = для_игрока, 100);
Добавить_границу(поле[i, j+1], поле[i, j].область)
END;
IF поле[i-1, j+1].область = NIL THEN
IF поле[i-1, j+1].точка_игрока = для_игрока THEN
Добавить_границу(поле[i-1, j+1], поле[i, j].область)
END
END;
IF поле[i-1, j].область = NIL THEN
ASSERT(поле[i-1, j].точка_игрока = для_игрока, 100);
Добавить_границу(поле[i-1, j], поле[i, j].область)
END;
IF поле[i-1, j-1].область = NIL THEN
IF поле[i-1, j-1].точка_игрока = для_игрока THEN
Добавить_границу(поле[i-1, j-1], поле[i, j].область)
END
END
END
END
END
END Рассчитать_границы;
PROCEDURE Посчитать_пленных (поле: Поле; для_игрока: INTEGER; OUT n: INTEGER);
VAR i, j: INTEGER;
BEGIN
n := 0;
FOR i := 0 TO LEN(поле)-1 DO
FOR j := 0 TO LEN(поле, 1)-1 DO
IF (поле[i, j].область # NIL) & (поле[i, j].область.игрок = для_игрока)
& (поле[i, j].точка_игрока >= 0) & (поле[i, j].точка_игрока # для_игрока) THEN
поле[i, j].была_в_плену := TRUE;
INC(поле[i, j].область.точек_противника);
INC(n)
END
END
END
END Посчитать_пленных;
END ТочкиИгра.
Модуль ТочкиХранение
MODULE ТочкиХранение;
IMPORT Игра := ТочкиИгра, Tm := TextMappers;
TYPE
Ход* = POINTER TO RECORD
след*: Ход;
x*, y*: INTEGER;
END;
PROCEDURE История* (поле: Игра.Поле): Ход;
VAR нач, кон: Ход; номер_хода: INTEGER; x, y: INTEGER;
PROCEDURE Найти_ход;
BEGIN
x := 0; y := 0;
WHILE (y < LEN(поле, 1))
& ~( (поле[x, y].точка_игрока # -1)
& (поле[x, y].поставлена_на_ходе = номер_хода) ) DO
INC(x);
IF x >= LEN(поле) THEN
x :=0;
INC(y)
END
END
END Найти_ход;
BEGIN
номер_хода := 0;
Найти_ход;
WHILE y < LEN(поле, 1) DO
IF кон # NIL THEN
NEW(кон.след);
кон := кон.след
ELSE
NEW(нач);
кон := нач
END;
кон.x := x;
кон.y := y;
INC(номер_хода);
Найти_ход;
END;
RETURN нач
END История;
PROCEDURE В_текст* (история: Ход; VAR fm: Tm.Formatter);
VAR n: INTEGER;
BEGIN
n := 1;
WHILE история # NIL DO
fm.WriteInt(n);
fm.WriteString(' - (' );
fm.WriteInt(история.x);
fm.WriteString(', ');
fm.WriteInt(история.y);
fm.WriteString(')');
fm.WriteLn;
история := история.след;
INC(n)
END;
fm.WriteChar('#')
END В_текст;
PROCEDURE Из_текста* (VAR sc: Tm.Scanner): Ход;
VAR x, y: INTEGER; усп: BOOLEAN;
нач, кон: Ход;
PROCEDURE Взять_ход;
BEGIN
sc.Scan; (* читать дефис *)
sc.Scan; (* читать скобку *)
sc.Scan;
IF sc.type = Tm.int THEN
x := sc.int;
sc.Scan; (* читать запятую *)
sc.Scan;
IF sc.type = Tm.int THEN
y := sc.int;
sc.Scan; (* читать скобку *)
усп := TRUE
ELSE
усп := FALSE
END
ELSE
усп := FALSE
END
END Взять_ход;
BEGIN
sc.Scan; усп := TRUE;
WHILE усп & ~( (sc.type = Tm.char) & (sc.char = '#') OR (sc.type = Tm.eot) ) DO
Взять_ход;
IF усп THEN
IF нач = NIL THEN
NEW(нач);
кон := нач
ELSE
NEW(кон.след);
кон := кон.след
END;
кон.x := x;
кон.y := y;
sc.Scan
END
END;
RETURN нач
END Из_текста;
END ТочкиХранение.
Модуль ТочкиГрафика
MODULE ТочкиГрафика;
IMPORT Views, Игра := ТочкиИгра, Properties, Controllers,
Fonts, Strings, Stores, Хранение := ТочкиХранение;
CONST
мм = 36000;
клетка = 5 * мм;
поля = 5 * мм;
радиус_точки = мм * 7 DIV 6;
размер_шрифта = 12 * Fonts.point;
для_всех_игроков* = -1;
version = 0;
TYPE
View = POINTER TO RECORD (Views.View)
игра: Игра.Игра;
игрок: INTEGER;
последние_ходы_игроков: ARRAY 16 OF Ход;
время_игроков: ARRAY 16 OF INTEGER;
карандаш: RECORD
x, y: INTEGER
END;
для_игрока: INTEGER
END;
Ход = RECORD
x, y: INTEGER
END;
VAR
цвета_игроков*: ARRAY 16 OF INTEGER;
цвет_фона*, цвет_шрифта*, цвет_сетки*, цвет_последних_ходов*: INTEGER;
PROCEDURE^ Рисовать_карандаш (f: Views.Frame; l, t: INTEGER; x, y, ш, в: INTEGER;
цвет: INTEGER; пассивен: BOOLEAN);
PROCEDURE^ Рисовать_статус (v: View; f: Views.Frame; l, t: INTEGER);
PROCEDURE^ Рисовать_статистику (f: Views.Frame; l, t: INTEGER; игроков: INTEGER;
статистика: Игра.Статистика);
PROCEDURE^ Рисовать_сетку (f: Views.Frame; l, t: INTEGER; ш, в: INTEGER);
PROCEDURE^ Рисовать_точки (f: Views.Frame; l, t: INTEGER; поле: Игра.Поле);
PROCEDURE^ Рисовать_последние_ходы (f: Views.Frame; l, t: INTEGER;
IN ходы: ARRAY OF Ход; n: INTEGER);
PROCEDURE^ Рисовать_границы (f: Views.Frame; l, t: INTEGER; поле: Игра.Поле);
PROCEDURE (v: View) GetBackground (VAR color: INTEGER);
BEGIN
color := цвет_фона
END GetBackground;
PROCEDURE (v: View) Restore (f: Views.Frame; l, t, r, b: INTEGER);
VAR i, j: INTEGER;
BEGIN
Рисовать_статистику(f, 0, 0, v.игра.игроков, v.игра.статистика);
Рисовать_сетку(f, поля, v.игра.игроков * размер_шрифта + поля,
LEN(v.игра.поле), LEN(v.игра.поле, 1));
Рисовать_последние_ходы(f, поля, v.игра.игроков * размер_шрифта + поля,
v.последние_ходы_игроков, v.игра.игроков);
Рисовать_точки(f, поля, v.игра.игроков * размер_шрифта + поля, v.игра.поле);
Рисовать_границы(f, поля, v.игра.игроков * размер_шрифта + поля, v.игра.поле);
IF (v.карандаш.x # -1) & (v.карандаш.y # -1) THEN
Рисовать_карандаш(f, поля, v.игра.игроков * размер_шрифта + поля,
v.карандаш.x, v.карандаш.y, LEN(v.игра.поле), LEN(v.игра.поле, 1), цвета_игроков[v.игрок],
(v.для_игрока # -1) & (v.игрок # v.для_игрока))
END;
Рисовать_статус(v, f, поля, v.игра.игроков * размер_шрифта
+ поля + (LEN(v.игра.поле, 1) - 1) * клетка + поля)
END Restore;
PROCEDURE Рисовать_карандаш (f: Views.Frame; l, t: INTEGER;
x, y, ш, в: INTEGER; цвет: INTEGER; пассивен: BOOLEAN);
BEGIN
f.DrawLine(l + x * клетка - клетка DIV 2, t + y * клетка - клетка DIV 2,
l + x * клетка + клетка DIV 2, t + y * клетка + клетка DIV 2, 0, цвет);
f.DrawLine(l + x * клетка - клетка DIV 2, t + y * клетка + клетка DIV 2,
l + x * клетка + клетка DIV 2, t + y * клетка - клетка DIV 2, 0, цвет);
IF пассивен THEN
f.DrawLine(l + x * клетка - клетка DIV 2, t + y * клетка - клетка DIV 2,
l + x * клетка + клетка DIV 2,t + y * клетка - клетка DIV 2, 0, цвет);
f.DrawLine(l + x * клетка - клетка DIV 2, t + y * клетка + клетка DIV 2,
l + x * клетка + клетка DIV 2, t + y * клетка + клетка DIV 2, 0, цвет);
END
END Рисовать_карандаш;
PROCEDURE Рисовать_статус (v: View; f: Views.Frame; l, t: INTEGER);
VAR font: Fonts.Font; asc, dsc, w: INTEGER;
txt: ARRAY 128 OF CHAR;
s: ARRAY 16 OF CHAR;
BEGIN
font := Fonts.dir.Default();
font := Fonts.dir.This(font.typeface, размер_шрифта, font.style, font.weight);
font.GetBounds(asc, dsc, w);
Strings.IntToString(v.игра.ход + 1, s);
txt := s + '-й ход. ';
IF v.карандаш.x > -1 THEN
txt := txt + ' (';
Strings.IntToString(v.карандаш.x, s);
txt := txt + s + ', ';
Strings.IntToString(v.карандаш.y, s);
txt := txt + s + ')'
END;
f.DrawString(l, t + asc, цвет_шрифта, txt, font)
END Рисовать_статус;
PROCEDURE Рисовать_статистику (f: Views.Frame; l, t: INTEGER;
игроков: INTEGER; статистика: Игра.Статистика);
VAR i: INTEGER; font: Fonts.Font; asc, dsc, w: INTEGER;
txt: ARRAY 128 OF CHAR;
s: ARRAY 16 OF CHAR;
BEGIN
font := Fonts.dir.Default();
font := Fonts.dir.This(font.typeface, размер_шрифта, font.style, font.weight);
font.GetBounds(asc, dsc, w);
FOR i := 0 TO игроков-1 DO
txt := ' Пленных: ';
Strings.IntToString(статистика.количество_пленных[i], s);
txt := txt + s + ' Территория: ';
Strings.IntToString(статистика.территория[i], s);
txt := txt + s;
f.DrawString(l, t +asc + размер_шрифта * i, цвета_игроков[i], txt, font)
END
END Рисовать_статистику;
PROCEDURE Рисовать_сетку (f: Views.Frame; l, t: INTEGER; ш, в: INTEGER);
VAR i: INTEGER;
BEGIN
FOR i := 0 TO ш - 1 DO
f.DrawLine(l + i * клетка, t, l + i * клетка,
t + (в - 1) * клетка, 0, цвет_сетки)
END;
FOR i := 0 TO в - 1 DO
f.DrawLine(l, t + i * клетка, l + (ш - 1) * клетка,
t + i * клетка, 0, цвет_сетки)
END
END Рисовать_сетку;
PROCEDURE^ Рисовать_узел (f: Views.Frame; x, y: INTEGER; IN узел: Игра.Узел);
PROCEDURE Рисовать_точки (f: Views.Frame; l, t: INTEGER; поле: Игра.Поле);
VAR i, j: INTEGER;
BEGIN
FOR i := 0 TO LEN(поле) - 1 DO
FOR j := 0 TO LEN(поле, 1) - 1 DO
Рисовать_узел(f, l + i * клетка, t + j * клетка, поле[i, j])
END
END
END Рисовать_точки;
PROCEDURE Рисовать_свободную (f: Views.Frame; x, y: INTEGER; игрок: INTEGER);
BEGIN
f.DrawOval(x - радиус_точки, y - радиус_точки, x + радиус_точки,
y + радиус_точки, -1, цвета_игроков[игрок])
END Рисовать_свободную;
PROCEDURE Рисовать_взятую (f: Views.Frame; x, y: INTEGER; чья, кем: INTEGER);
BEGIN
f.DrawOval(x - радиус_точки, y - радиус_точки, x + радиус_точки,
y + радиус_точки, 0, цвета_игроков[кем])
END Рисовать_взятую;
PROCEDURE Рисовать_узел (f: Views.Frame; x, y: INTEGER; IN узел: Игра.Узел);
BEGIN
IF узел.точка_игрока >= 0 THEN
IF (узел.область = NIL) OR (узел.точка_игрока = узел.область.игрок) THEN
Рисовать_свободную(f, x, y, узел.точка_игрока)
ELSE
Рисовать_взятую(f, x, y, узел.точка_игрока, узел.точка_игрока)
END
END
END Рисовать_узел;
PROCEDURE Рисовать_последние_ходы (f: Views.Frame; l, t: INTEGER;
IN ходы: ARRAY OF Ход; n: INTEGER);
VAR i: INTEGER; x, y: INTEGER;
BEGIN
FOR i := 0 TO n-1 DO
IF ходы[i].x > -1 THEN
x := l + клетка*ходы[i].x;
y := t + клетка*ходы[i].y;
f.DrawOval(x - 2*радиус_точки, y - 2*радиус_точки, x + 2*радиус_точки,
y + 2*радиус_точки, 0, цвет_последних_ходов)
END
END
END Рисовать_последние_ходы;
PROCEDURE Внутренняя_клетка (поле: Игра.Поле; l, t: INTEGER;
для_игрока: INTEGER): BOOLEAN;
VAR n: INTEGER;
BEGIN
RETURN (0 <= l) & (l <= LEN(поле) - 2) & (0 <= t) & (t <= LEN(поле, 1) - 2)
& (
(поле[l, t].область # NIL) & (поле[l, t].область.игрок = для_игрока) &
(поле[l+1, t+1].область = поле[l, t].область)
OR
(поле[l+1, t].область # NIL) & (поле[l+1, t].область.игрок = для_игрока) &
(поле[l, t+1].область = поле[l+1, t].область)
)
END Внутренняя_клетка;
PROCEDURE Идёт_граница (IN у1, у2: Игра.Узел): BOOLEAN;
VAR
BEGIN
RETURN (у1.граница[0] # NIL)
& ((у1.граница[0] = у2.граница[0]) OR (у1.граница[0] = у2.граница[1]))
OR (у1.граница[1] # NIL)
& ((у1.граница[1] = у2.граница[0]) OR (у1.граница[1] = у2.граница[1]))
END Идёт_граница;
PROCEDURE Рисовать_границы (f: Views.Frame; l, t: INTEGER; поле: Игра.Поле);
VAR i, j: INTEGER;
PROCEDURE Вверх_вправо (x, y: INTEGER);
BEGIN
IF Идёт_граница(поле[x, y-1], поле[x, y]) THEN
f.DrawLine(l + x * клетка, t + y * клетка, l + x * клетка,
t + (y - 1) * клетка, 0, цвета_игроков[поле[x, y].точка_игрока])
ELSIF Идёт_граница(поле[x+1, y], поле[x, y]) THEN
f.DrawLine(l + x * клетка, t + y * клетка, l + (x + 1) * клетка,
t + y * клетка, 0, цвета_игроков[поле[x, y].точка_игрока])
ELSIF Идёт_граница(поле[x+1, y-1], поле[x, y])THEN
IF ~Внутренняя_клетка(поле, x, y-1, поле[x, y].точка_игрока) THEN
f.DrawLine(l + x * клетка, t + y * клетка, l + (x + 1) * клетка,
t + (y - 1) * клетка, 0, цвета_игроков[поле[x, y].точка_игрока])
END
END
END Вверх_вправо;
PROCEDURE Вниз_вправо (x, y: INTEGER);
BEGIN
IF Идёт_граница(поле[x+1, y], поле[x, y]) THEN
f.DrawLine(l + x * клетка, t + y * клетка, l + (x + 1) * клетка,
t + y * клетка, 0, цвета_игроков[поле[x, y].точка_игрока])
ELSIF Идёт_граница(поле[x, y+1], поле[x, y]) THEN
f.DrawLine(l + x * клетка, t + y * клетка, l + x * клетка,
t + (y + 1) * клетка, 0, цвета_игроков[поле[x, y].точка_игрока])
ELSIF Идёт_граница(поле[x+1, y+1], поле[x, y]) THEN
IF ~Внутренняя_клетка(поле, x, y, поле[x, y].точка_игрока) THEN
f.DrawLine(l + x * клетка, t + y * клетка, l + (x + 1) * клетка,
t + (y + 1) * клетка, 0, цвета_игроков[поле[x, y].точка_игрока])
END
END
END Вниз_вправо;
PROCEDURE Вверх (x, y: INTEGER);
BEGIN
IF Идёт_граница(поле[x, y-1], поле[x, y]) THEN
f.DrawLine(l + x * клетка, t + y * клетка, l + x * клетка,
t + (y - 1) * клетка, 0, цвета_игроков[поле[x, y].точка_игрока])
END
END Вверх;
BEGIN
FOR i := 0 TO LEN(поле) - 2 DO
FOR j := 1 TO LEN(поле, 1) - 2 DO
IF поле[i, j].граница[0] # NIL THEN
Вверх_вправо(i, j);
Вниз_вправо(i, j)
END
END
END;
FOR i := 0 TO LEN(поле) - 2 DO
IF поле[i, 0].граница[0] # NIL THEN
Вниз_вправо(i, 0)
END;
IF поле[i, LEN(поле, 1) - 1].граница[0] # NIL THEN
Вверх_вправо(i, LEN(поле, 1) - 1)
END
END;
FOR j := 1 TO LEN(поле, 1) - 1 DO
IF поле[LEN(поле) - 1, j].граница[0] # NIL THEN
Вверх(LEN(поле) - 1, j)
END
END
END Рисовать_границы;
PROCEDURE (v: View) HandlePropMsg (VAR msg: Properties.Message);
BEGIN
WITH msg: Properties.SizePref DO
msg.w := 2 * поля + (LEN(v.игра.поле) - 1) * клетка;
msg.h := v.игра.игроков*размер_шрифта + 2 * поля
+ (LEN(v.игра.поле, 1) - 1) * клетка + 2 * размер_шрифта;
msg.fixedW := TRUE;
msg.fixedH := TRUE;
ELSE
END
END HandlePropMsg;
PROCEDURE^ Щелчок_по_узлу (v: View; x, y: INTEGER);
PROCEDURE (v: View) HandleCtrlMsg (f: Views.Frame; VAR msg:
Views.CtrlMessage; VAR focus: Views.View);
VAR x, y: INTEGER;
BEGIN
WITH msg: Controllers.TrackMsg DO
x := msg.x - (поля - клетка DIV 2);
y := msg.y - (v.игра.игроков*размер_шрифта + поля - клетка DIV 2);
x := x DIV клетка;
y := y DIV клетка;
IF (0 <= x) & (x < LEN(v.игра.поле)) & (0 <= y ) & (y < LEN(v.игра.поле, 1)) THEN
IF (v.для_игрока = -1) OR (v.для_игрока = v.игрок) THEN
Щелчок_по_узлу(v, x, y);
Views.Update(v, Views.keepFrames)
END
END
| msg: Controllers.PollCursorMsg DO
x := msg.x - (поля - клетка DIV 2);
y := msg.y - (v.игра.игроков*размер_шрифта + поля - клетка DIV 2);
x := x DIV клетка;
y := y DIV клетка;
IF (0 <= x) & (x < LEN(v.игра.поле)) & (0 <= y) & (y < LEN(v.игра.поле, 1)) THEN
ELSE
x := -1; y := -1
END;
IF (x # v.карандаш.x) OR (y # v.карандаш.y) THEN
v.карандаш.x := x;
v.карандаш.y := y;
Views.Update(v, Views.keepFrames)
END
ELSE
END
END HandleCtrlMsg;
PROCEDURE^ Принять_ход* (v: Views.View; x, y: INTEGER);
PROCEDURE Щелчок_по_узлу (v: View; x, y: INTEGER);
BEGIN
IF (v.игра.поле[x, y].точка_игрока = -1)
& ((v.игра.поле[x, y].область = NIL)
OR (v.игра.поле[x, y].область.точек_противника = 0)) THEN
Принять_ход(v, x, y)
END
END Щелчок_по_узлу;
PROCEDURE Эта_игра* (v: Views.View): Игра.Игра;
BEGIN
WITH v: View DO
RETURN v.игра
ELSE
RETURN NIL
END
END Эта_игра;
PROCEDURE Принять_ход* (v: Views.View; x, y: INTEGER);
BEGIN
WITH v: View DO
ASSERT((v.игра.поле[x, y].точка_игрока = -1)
& ((v.игра.поле[x, y].область = NIL)
OR (v.игра.поле[x, y].область.точек_противника = 0)), 20);
v.игра.Сделать_ход(x, y, v.игрок);
v.последние_ходы_игроков[v.игрок].x := x;
v.последние_ходы_игроков[v.игрок].y := y;
v.игрок := (v.игрок + 1) MOD v.игра.игроков;
Views.Update(v, Views.keepFrames);
Views.SetDirty(v)
END
END Принять_ход;
PROCEDURE Ходит_игрок* (v: Views.View): INTEGER;
BEGIN
WITH v: View DO
RETURN v.игрок
END
END Ходит_игрок;
PROCEDURE Последний_ход* (v: Views.View; OUT x, y: INTEGER);
VAR игрок: INTEGER;
BEGIN
WITH v: View DO
игрок := (v.игрок - 1) MOD v.игра.игроков;
x := v.последние_ходы_игроков[игрок].x;
y := v.последние_ходы_игроков[игрок].y
END
END Последний_ход;
PROCEDURE Сделать_для_игрока* (v: Views.View; игрок: INTEGER);
BEGIN
ASSERT(игрок >= для_всех_игроков, 20);
WITH v: View DO
v.для_игрока := игрок
END
END Сделать_для_игрока;
PROCEDURE Для_какого_игрока* (v: Views.View): INTEGER;
BEGIN
WITH v: View DO
RETURN v.для_игрока
END
END Для_какого_игрока;
PROCEDURE Init (v: View);
VAR i: INTEGER;
BEGIN
v.игрок := 0;
v.карандаш.x := -1;
v.карандаш.y := -1;
v.для_игрока := для_всех_игроков;
FOR i := 0 TO LEN(v.последние_ходы_игроков)-1 DO
v.последние_ходы_игроков[i].x := -1;
v.последние_ходы_игроков[i].y := -1
END;
END Init;
PROCEDURE Новое_поле* (игра: Игра.Игра): Views.View;
VAR v: View;
BEGIN
ASSERT(игра # NIL, 20);
NEW(v);
Init(v);
v.игра := игра;
RETURN v
END Новое_поле;
(* Сериализация *)
PROCEDURE (v: View) Externalize (VAR wr: Stores.Writer);
VAR ходы: Хранение.Ход; i: INTEGER;
BEGIN
wr.WriteVersion(version);
wr.WriteInt(LEN(v.игра.поле));
wr.WriteInt(LEN(v.игра.поле, 1));
wr.WriteInt(v.игра.игроков);
wr.WriteInt(v.игра.ход);
ходы := Хранение.История(v.игра.поле);
FOR i := 1 TO v.игра.ход DO
wr.WriteInt(ходы.x);
wr.WriteInt(ходы.y);
ходы := ходы.след
END;
END Externalize;
PROCEDURE (v: View) Internalize (VAR rd: Stores.Reader);
VAR ver: INTEGER; ш, в, игроков, ход: INTEGER; i, x, y: INTEGER;
BEGIN
Init(v);
rd.ReadVersion(version, version, ver);
IF ~rd.cancelled THEN
rd.ReadInt(ш);
rd.ReadInt(в);
rd.ReadInt(игроков);
rd.ReadInt(ход);
v.игра := Игра.Новая_игра(ш, в, игроков);
v.игрок := 0;
FOR i := 1 TO ход DO
rd.ReadInt(x);
rd.ReadInt(y);
Принять_ход(v, x, y)
END
END
END Internalize;
PROCEDURE (v: View) CopyFromSimpleView (source: Views.View);
VAR ходы: Хранение.Ход;
BEGIN
WITH source: View DO
Init(v);
v.игра := Игра.Новая_игра(LEN(source.игра.поле),
LEN(source.игра.поле, 1), source.игра.игроков);
ходы := Хранение.История(source.игра.поле);
WHILE ходы # NIL DO
Принять_ход(v, ходы.x, ходы.y);
ходы := ходы.след
END
END
END CopyFromSimpleView;
BEGIN
цвета_игроков[0] := 0C0C0C0H;
цвета_игроков[1] := 00000C0H;
цвета_игроков[2] := 0C00000H;
цвета_игроков[3] := 0C000H;
цвет_фона := 0;
цвет_шрифта := 0C0C0C0H;
цвет_сетки := 0303030H;
цвет_последних_ходов := 0505050H;
END ТочкиГрафика.
Модуль ТочкиКоманды
MODULE ТочкиКоманды;
IMPORT Views, Игра := ТочкиИгра, Графика := ТочкиГрафика, Net := CommStreams,
DevCommanders, Services, Log := StdLog, Tm := TextMappers, Windows, TextModels,
TextViews, Хранение := ТочкиХранение;
VAR
ширина*, высота*: INTEGER;
число_игроков*: INTEGER;
история: Хранение.Ход;
поле: Views.View;
далее_на*: INTEGER;
до_хода*: INTEGER;
PROCEDURE Открыть*;
VAR игра: Игра.Игра;
v: Views.View;
BEGIN
игра := Игра.Новая_игра(ширина, высота, число_игроков);
v := Графика.Новое_поле(игра);
Views.OpenView(v)
END Открыть;
PROCEDURE Новая_игра*;
VAR sc: Tm.Scanner;
BEGIN
ASSERT(DevCommanders.par.text # NIL, 20);
sc.ConnectTo(DevCommanders.par.text);
sc.SetPos(DevCommanders.par.beg);
sc.Scan; ASSERT(sc.type = Tm.int, 21);
ширина := sc.int;
sc.Scan; ASSERT((sc.type = Tm.string) & (sc.string = "x"), 22);
sc.Scan; ASSERT(sc.type = Tm.int, 23);
высота := sc.int;
sc.Scan; ASSERT((sc.type = Tm.string) & (sc.string = "игроков"), 24);
sc.Scan; ASSERT(sc.type = Tm.int, 25);
число_игроков := sc.int
END Новая_игра;
PROCEDURE Сохранить_историю*;
VAR w: Windows.Window;
игра: Игра.Игра;
tm: TextModels.Model;
история: Хранение.Ход;
fm: Tm.Formatter;
tv: TextViews.View;
BEGIN
w := Windows.dir.First();
WHILE (w # NIL) & ~ ( Графика.Эта_игра(w.doc.ThisView()) # NIL ) DO
w := Windows.dir.Next(w)
END;
IF w # NIL THEN
игра := Графика.Эта_игра(w.doc.ThisView());
tm := TextModels.dir.New();
fm.ConnectTo(tm);
fm.WriteView(DevCommanders.dir.New());
fm.WriteString('"ТочкиКоманды.Загрузить_историю;StdCmds.OpenToolDialog('
+ "'Точки/Rsrc/Проигрыватель', 'Вперёд'" + ')"');
fm.WriteLn;
fm.WriteInt(LEN(игра.поле));
fm.WriteString(' x ');
fm.WriteInt(LEN(игра.поле, 1));
fm.WriteString(' игроков ');
fm.WriteInt(игра.игроков);
fm.WriteLn;
история := Хранение.История(игра.поле);
Хранение.В_текст(история, fm);
tv := TextViews.dir.New(tm);
Views.OpenView(tv)
END
END Сохранить_историю;
PROCEDURE Загрузить_историю*;
VAR sc: Tm.Scanner; ш, в, игроков: INTEGER; игра: Игра.Игра;
BEGIN
ASSERT(DevCommanders.par.text # NIL, 20);
sc.ConnectTo(DevCommanders.par.text);
sc.SetPos(DevCommanders.par.beg);
sc.Scan; ASSERT(sc.type = Tm.int, 21);
ш := sc.int;
sc.Scan; ASSERT((sc.type = Tm.string) & (sc.string = "x"), 22);
sc.Scan; ASSERT(sc.type = Tm.int, 23);
в := sc.int;
sc.Scan; ASSERT((sc.type = Tm.string) & (sc.string = "игроков"), 24);
sc.Scan; ASSERT(sc.type = Tm.int, 25);
игроков := sc.int;
история := Хранение.Из_текста(sc);
игра := Игра.Новая_игра(ш, в, игроков);
поле := Графика.Новое_поле(игра);
Views.OpenView(поле)
END Загрузить_историю;
PROCEDURE Далее*;
VAR n: INTEGER;
BEGIN
IF далее_на <= 0 THEN
далее_на := 1
END;
n := далее_на;
WHILE (история # NIL) & (n > 0 ) DO
Графика.Принять_ход(поле, история.x, история.y);
история := история.след;
DEC(n)
END
END Далее;
PROCEDURE До_конца*;
BEGIN
WHILE история # NIL DO
Графика.Принять_ход(поле, история.x, история.y);
история := история.след
END
END До_конца;
PROCEDURE До_хода*;
BEGIN
IF до_хода <= 0 THEN
до_хода := 1
END;
IF поле # NIL THEN
WHILE (история # NIL) & (Графика.Эта_игра(поле).ход < до_хода-1) DO
Графика.Принять_ход(поле, история.x, история.y);
история := история.след
END
END
END До_хода;
PROCEDURE Настроить_цвета*;
VAR rd: TextModels.Reader;
цв: INTEGER; i: INTEGER;
PROCEDURE Читать_цвет;
BEGIN
rd.Read;
WHILE ~rd.eot & ~ ( (rd.char # " ") & (rd.char # TextModels.tab) ) DO
rd.Read
END;
цв := rd.attr.color;
rd.Read;
WHILE ~rd.eot & ~ ( (rd.char = " ") OR (rd.char = TextModels.tab) ) DO
rd.Read
END;
END Читать_цвет;
BEGIN
ASSERT(DevCommanders.par.text # NIL, 20);
rd := DevCommanders.par.text.NewReader(NIL);
rd.SetPos(DevCommanders.par.beg);
Читать_цвет;
Графика.цвет_фона := цв;
Читать_цвет;
Графика.цвет_сетки := цв;
Читать_цвет;
Графика.цвет_шрифта := цв;
Читать_цвет;
Графика.цвет_последних_ходов := цв;
FOR i := 0 TO 3 DO
Читать_цвет;
Графика.цвета_игроков[i] := цв
END
END Настроить_цвета;
BEGIN
ширина := 39;
высота := 32;
число_игроков := 2;
далее_на := 1;
до_хода := 1
END ТочкиКоманды.
Модуль ТочкиСетевая
MODULE ТочкиСетевая;
IMPORT Services, Net := CommStreams, Игра := ТочкиИгра, Графика := ТочкиГрафика,
Views, Dialog, Windows, DevCommanders, Tm := TextMappers;
CONST
ждём_локального_хода = 1;
ждём_сетевого_хода = 2;
timeout = 5000;
интервал_подачи_сигнала = 10000;
TYPE
Server = POINTER TO RECORD (Services.Action)
in: Net.Listener;
clients: ClientItem
END;
ClientItem = POINTER TO RECORD
next: ClientItem;
s: Net.Stream
END;
Msg = ARRAY 4 OF BYTE;
Client = POINTER TO RECORD (Services.Action)
s: Net.Stream;
игра: Игра.Игра;
поле: Views.View;
для_игрока: INTEGER;
состояние: INTEGER;
время_ожидания_сетевых_ходов: LONGINT
END;
VAR
stop: BOOLEAN;
PROCEDURE Receive (s: Net.Stream; OUT msg: Msg; OUT n: INTEGER);
VAR i: INTEGER;
t: LONGINT;
BEGIN
n := 0;
s.ReadBytes(msg, 0, 4, i);
IF i > 0 THEN
t := Services.Ticks();
n := i;
WHILE (n < LEN(msg)) & (Services.Ticks() - t < timeout) DO
s.ReadBytes(msg, n, 4-n, i);
INC(n, i)
END
END
END Receive;
PROCEDURE Transmit (s: Net.Stream; OUT msg: Msg; OUT n: INTEGER);
VAR i: INTEGER;
t: LONGINT;
BEGIN
n := 0;
t := Services.Ticks();
REPEAT
s.WriteBytes(msg, n, 4-n, i);
INC(n, i)
UNTIL (n = LEN(msg)) OR (Services.Ticks() - t > timeout)
END Transmit;
PROCEDURE (srv: Server) Do;
VAR s: Net.Stream;
c: ClientItem;
msg: Msg;
n: INTEGER;
PROCEDURE Retranslate;
VAR to: ClientItem;
BEGIN
to := srv.clients;
WHILE to # NIL DO
IF (to # c) & to.s.IsConnected() THEN
Transmit(to.s, msg, n);
IF n < LEN(msg) THEN
to.s.Close
END
END;
to := to.next
END
END Retranslate;
BEGIN
IF ~ stop THEN
srv.in.Accept(s);
IF s # NIL THEN
NEW(c); c.s := s;
c.next := srv.clients;
srv.clients := c
END;
c := srv.clients;
WHILE c # NIL DO
IF c.s.IsConnected() THEN
Receive(c.s, msg, n);
IF n = LEN(Msg) THEN
Retranslate
ELSIF n > 0 THEN
c.s.Close
END
END;
c := c.next
END;
Services.DoLater(srv, Services.now)
ELSE
srv.in.Close
END
END Do;
PROCEDURE ^ Послать_ход (s: Net.Stream; x, y: INTEGER);
PROCEDURE (cl: Client) Do;
VAR i: INTEGER; x, y: INTEGER;
msg: Msg;
n: INTEGER;
BEGIN
IF cl.поле.context = NIL THEN (* окно было закрыто *)
cl.состояние := -1
END;
CASE cl.состояние OF
| ждём_локального_хода:
IF Графика.Ходит_игрок(cl.поле) # cl.для_игрока THEN
Графика.Последний_ход(cl.поле, x, y);
Послать_ход(cl.s, x, y);
IF cl.s.IsConnected() THEN
cl.состояние := ждём_сетевого_хода;
cl.время_ожидания_сетевых_ходов := Services.Ticks()
ELSE
cl.состояние := -1
END
END
| ждём_сетевого_хода:
Receive(cl.s, msg, n);
IF n = LEN(msg) THEN
x := msg[0] + msg[1]*256;
y := msg[2] + msg[3]*256;
Графика.Принять_ход(cl.поле, x, y);
IF Графика.Ходит_игрок(cl.поле) = cl.для_игрока THEN
cl.состояние := ждём_локального_хода;
IF Services.Ticks() - cl.время_ожидания_сетевых_ходов > интервал_подачи_сигнала THEN
Dialog.Beep
END
ELSE
cl.состояние := ждём_сетевого_хода
END
ELSIF (n = 0) & cl.s.IsConnected() THEN
cl.состояние := ждём_сетевого_хода
ELSE
cl.состояние := -1
END
END;
IF cl.состояние # -1 THEN
Services.DoLater(cl, Services.now)
ELSE
Dialog.ShowMsg('Ошибка сетевого подключения!');
cl.s.Close
END
END Do;
PROCEDURE Послать_ход (s: Net.Stream; x, y: INTEGER);
VAR msg: Msg;
n: INTEGER;
BEGIN
msg[0] := SHORT(SHORT(x MOD 256));
msg[1] := SHORT(SHORT(x DIV 256));
msg[2] := SHORT(SHORT(y MOD 256));
msg[3] := SHORT(SHORT(y DIV 256));
Transmit(s, msg, n);
IF n < LEN(msg) THEN
s.Close
END
END Послать_ход;
PROCEDURE Пустить_сервер*;
VAR sc: Tm.Scanner;
srv: Server;
res: INTEGER;
BEGIN
stop := FALSE;
ASSERT(DevCommanders.par.text # NIL, 20);
sc.ConnectTo(DevCommanders.par.text);
sc.SetPos(DevCommanders.par.beg);
sc.Scan; ASSERT(sc.type = Tm.string, 21);
NEW(srv);
Net.NewListener("CommTCP", sc.string, srv.in, res);
IF srv.in # NIL THEN
Services.DoLater(srv, Services.now)
ELSE
Dialog.ShowMsg("Не удалось открыть локальный порт!")
END
END Пустить_сервер;
PROCEDURE Остановить_сервера*;
BEGIN
stop := TRUE
END Остановить_сервера;
PROCEDURE Подключить*;
VAR cl: Client;
w: Windows.Window;
sc: Tm.Scanner;
adr: ARRAY 256 OF CHAR;
для_игрока: INTEGER;
res: INTEGER;
BEGIN
ASSERT(DevCommanders.par.text # NIL, 20);
sc.ConnectTo(DevCommanders.par.text);
sc.SetPos(DevCommanders.par.beg);
sc.Scan; ASSERT(sc.type = Tm.string, 21);
adr := sc.string$;
sc.Scan; ASSERT((sc.type = Tm.string), 22);
IF sc.string = "игрока" THEN
sc.Scan; ASSERT((sc.type = Tm.int), 23);
для_игрока := sc.int
ELSIF sc.string = "наблюдателя" THEN
для_игрока := 100
ELSE
HALT(24)
END;
w := Windows.dir.First();
WHILE (w # NIL) & ~ ( Графика.Эта_игра(w.doc.ThisView()) # NIL ) DO
w := Windows.dir.Next(w)
END;
IF w # NIL THEN
NEW(cl);
Net.NewStream("CommTCP", "0.0.0.0:0", adr, cl.s, res);
IF cl.s # NIL THEN
cl.поле := w.doc.ThisView();
cl.игра := Графика.Эта_игра(cl.поле);
cl.для_игрока := для_игрока;
Графика.Сделать_для_игрока(cl.поле, для_игрока);
IF cl.для_игрока = 0 THEN
cl.состояние := ждём_локального_хода
ELSE
cl.состояние := ждём_сетевого_хода
END;
Services.DoLater(cl, Services.now)
ELSE
Dialog.ShowMsg("Не удалось подключиться к серверу!")
END
ELSE
Dialog.ShowMsg("Нет открытого окна с игрой!")
END
END Подключить;
END ТочкиСетевая.

