Заметки за 2022 год

Крестики-нолики

После 2004 года я очень долго не касался темы создания игр, полностью уйдя в вебдев. Хотя, пробовал всякие движки, но это были просто эксперименты не заслуживающие внимания.

В 2020 году, в самый разгар пандемии коронавируса, я решил закрыть гештальт по играм. Засел за «GameMaker Studio 2» от «YoYo Games». В результате появилась эта игра.

Крестики-нолики
Крестики-нолики

Возможно возникнет вопрос, почему в предыдущей заметке практически всё было написано на C++, а теперь такой шаг назад. Отвечу, мне не хотелось писать отрисовку, ввод и прочие сущности «движка». Я решил, что лучше будет сосредоточиться на игровой логике.

О игре

В начале игры случайным образом определяется кто будет первым ходить. Затем, в случае если первым ходит игрок, Вы при помощи мыши выбираете ячейку для своего хода и кликаете левой кнопкой мыши. Если же ходит компьютер, то ожидаем конца его хода до появления надписи «YOU TURN».

Ссылка на загрузку игры: OXO.zip

Код игры содержит всего два объекта obj_game и obj_tile. Девять скриптов поделённых на зоны ответственности для спрайтов, ИИ компьютера и игровой доски. А также ресурсы в виде восьми спрайтов и двух шрифтов.

Все ассеты видно на скриншоте ниже.

Код игры в GMS2
Код игры в GMS2

Объект obj_game является главным. Он обрабатывает события:

  • Create – Инициализация
  • Clean Up – Очистка данных
  • Step – Обработчик основного цикла
  • Draw – Вывод UI
  • Key Up – Нажатие клавиши

Для примера код обработки события Step в объекте obj_game. Здесь же вызываются скрипты scr_computer_turn и scr_test_board_to_win.

 1/// @description Обработка ситуации
 2
 3// Если в игре
 4if (in_game) {
 5
 6	// Если ход компьютера
 7	if (turn == 1) {
 8
 9		message = "WAIT";
10
11		if (computer_awaiting < 1) {
12			// Ход компьютера
13			scr_computer_turn();
14		
15			// Передаём ход игроку
16			turn = 0;
17			message = "YOU TURN";
18		
19			// Проверяем победителя
20			scr_test_board_to_win();
21			
22			computer_awaiting = room_speed;
23		}
24		
25		computer_awaiting -= 1;
26	}
27
28}

Второй объект obj_tile обрабатывает только клик мышкой по тайлу в событии Left Pressed. Это ход игрока:

 1/// @description Клик по тайлу
 2
 3if (obj_game.turn == 0 && obj_game.in_game) {
 4	
 5	if (self.sprite_index == spr_n) {
 6		// Меняем спрайт на крестик
 7		self.sprite_index = spr_x;
 8		
 9		// Проставляем в таблице свой ход
10		grid_x = (self.x / 128) - 3;
11		grid_y = (self.y / 128) - 2;
12		ds_grid_set(obj_game.board, grid_x, grid_y, 1);
13			
14		// Передаём ход компьютеру
15		obj_game.turn = 1;
16		
17		with (obj_game) {
18			scr_test_board_to_win();
19		}
20	}
21
22}

Так компьютер расчитывает куда ему сходить:

 1function scr_computer_turn() {
 2	// Попытка сходить в выигрышную позицию
 3	pos = scr_computer_turn_win();
 4
 5	if (pos != false) {
 6		scr_computer_make_turn(pos[0], pos[1]);
 7		return;
 8	}
 9
10	// Попытка заблокировать выигрышную позицию игрока
11	pos = scr_computer_turn_block();
12
13	if (pos != false) {
14		scr_computer_make_turn(pos[0], pos[1]);
15		return;
16	}
17
18	// Ход в случайное пустое место
19	pos = scr_computer_get_random();
20
21	if (pos != false) {
22		scr_computer_make_turn(pos[0], pos[1]);
23	}
24}

Поиск возможной выигрышной позиции:

 1function scr_computer_turn_win() {
 2	board_x = noone;
 3	board_y = noone;
 4
 5	for(i = 0; i < 8; i++) {
 6	
 7		line = obj_game.lines[i];
 8	
 9		a = line[0];
10		b = line[1];
11		c = line[2];
12	
13		av = ds_grid_get(obj_game.board, a[0], a[1]);
14		bv = ds_grid_get(obj_game.board, b[0], b[1]);
15		cv = ds_grid_get(obj_game.board, c[0], c[1]);
16	
17		if (av == 1 || bv == 1 || cv == 1) { continue; }
18	
19		count_c = 0;
20		count_e = 0;
21	
22		if (av == 2) { count_c++; } else if (av == 0) { count_e++; board_x = a[0]; board_y = a[1] }
23		if (bv == 2) { count_c++; } else if (bv == 0) { count_e++; board_x = b[0]; board_y = b[1] }
24		if (cv == 2) { count_c++; } else if (cv == 0) { count_e++; board_x = c[0]; board_y = c[1] }
25	
26		if (count_c == 2 && count_e == 1) {
27			return [board_x, board_y];
28		}
29	}
30
31	return false;
32}

Приводить весь код я не вижу смысла, там всё достаточно просто.