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

После 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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/// @description Обработка ситуации

// Если в игре
if (in_game) {

	// Если ход компьютера
	if (turn == 1) {

		message = "WAIT";

		if (computer_awaiting < 1) {
			// Ход компьютера
			scr_computer_turn();
		
			// Передаём ход игроку
			turn = 0;
			message = "YOU TURN";
		
			// Проверяем победителя
			scr_test_board_to_win();
			
			computer_awaiting = room_speed;
		}
		
		computer_awaiting -= 1;
	}

}

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/// @description Клик по тайлу

if (obj_game.turn == 0 && obj_game.in_game) {
	
	if (self.sprite_index == spr_n) {
		// Меняем спрайт на крестик
		self.sprite_index = spr_x;
		
		// Проставляем в таблице свой ход
		grid_x = (self.x / 128) - 3;
		grid_y = (self.y / 128) - 2;
		ds_grid_set(obj_game.board, grid_x, grid_y, 1);
			
		// Передаём ход компьютеру
		obj_game.turn = 1;
		
		with (obj_game) {
			scr_test_board_to_win();
		}
	}

}

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function scr_computer_turn() {
	// Попытка сходить в выигрышную позицию
	pos = scr_computer_turn_win();

	if (pos != false) {
		scr_computer_make_turn(pos[0], pos[1]);
		return;
	}

	// Попытка заблокировать выигрышную позицию игрока
	pos = scr_computer_turn_block();

	if (pos != false) {
		scr_computer_make_turn(pos[0], pos[1]);
		return;
	}

	// Ход в случайное пустое место
	pos = scr_computer_get_random();

	if (pos != false) {
		scr_computer_make_turn(pos[0], pos[1]);
	}
}

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function scr_computer_turn_win() {
	board_x = noone;
	board_y = noone;

	for(i = 0; i < 8; i++) {
	
		line = obj_game.lines[i];
	
		a = line[0];
		b = line[1];
		c = line[2];
	
		av = ds_grid_get(obj_game.board, a[0], a[1]);
		bv = ds_grid_get(obj_game.board, b[0], b[1]);
		cv = ds_grid_get(obj_game.board, c[0], c[1]);
	
		if (av == 1 || bv == 1 || cv == 1) { continue; }
	
		count_c = 0;
		count_e = 0;
	
		if (av == 2) { count_c++; } else if (av == 0) { count_e++; board_x = a[0]; board_y = a[1] }
		if (bv == 2) { count_c++; } else if (bv == 0) { count_e++; board_x = b[0]; board_y = b[1] }
		if (cv == 2) { count_c++; } else if (cv == 0) { count_e++; board_x = c[0]; board_y = c[1] }
	
		if (count_c == 2 && count_e == 1) {
			return [board_x, board_y];
		}
	}

	return false;
}

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