Към съдържанието
wannacode

Как се мисли игра

Проблем

Откъде започва една игра?

Искаш да напишеш игра. Например надбягване със зар: фигурка тръгва от поле 0, хвърляш зар и тя върви напред, докато стигне поле 20.

Отваряш редактора и... какво пишеш най-напред? Менюто? Зара? Дъската? Ако започнеш от всичко наведнъж, след час имаш много код, който още не се пуска.

Помага да зададеш на играта пет въпроса, преди да пишеш код.

Идеята

Пет въпроса за всяка игра

  1. Състояние. Кои числа описват света сега? При надбягването е полето на фигурката и номерът на хода. При шаха е къде стои всяка фигура и кой е на ход.
  2. Правила. Какво е позволено и какво става след него? „Фигурката върви толкова полета, колкото е зарът.“
  3. Вход. Какво идва отвън? Изборът на играча или числото на зара.
  4. Цикъл. Ход след ход: вход, обновяване на състоянието, показване. После пак.
  5. Край. Кога играта свършва и кой печели? „Фигурката е на поле 20.“

Отговорите са почти готовият код: състоянието са променливи, правилата са функции, входът е ask() или генератор на случайни числа, цикълът е while или for, а краят е условието на цикъла.

Виж го

Ход след ход

const random = makeRandom(42);
let field = 0;
for (let turn = 1; field < 20; turn++) {
const roll = Math.floor(random() * 6) + 1;
field = Math.min(field + roll, 20);
console.log(`Ход ${turn}: зар ${roll}, поле ${field}`);
}
console.log("Финал! Фигурката стигна поле 20.");
Стъпка по стъпка, с думи
  1. Генератор със семе 42 и фигурка на поле 0.
  2. Ход 1: вход е зарът 2, състоянието става поле 2, екранът показва реда.
  3. Ход 2: вход е зарът 6, състоянието става поле 8, екранът показва реда.
  4. Ход 3: вход е зарът 1, състоянието става поле 9, екранът показва реда.
  5. Ход 4: вход е зарът 5, състоянието става поле 14, екранът показва реда.
  6. Ход 5: вход е зарът 1, състоянието става поле 15, екранът показва реда.
  7. Ход 6: вход е зарът 4, състоянието става поле 19, екранът показва реда.
  8. Ход 7: вход е зарът 1, състоянието става поле 20, екранът показва реда. Поле 20: условието field < 20 вече е невярно.
  9. Цикълът свърши, защото полето стигна 20. Това е краят на играта.

Цялата игра е един цикъл. На всеки ход идва вход (зарът), състоянието се обновява (новото поле) и се показва (един ред на екрана). Условието на цикъла е краят: докато полето е под 20, има следващ ход.

Math.min пази фигурката на дъската: ако зарът я изпрати след 20, тя спира на 20.

По-точно, за любопитните
  • Тази игра е ходова: нищо не се случва, докато не дойде следващият вход. Така са шахът, картите и повечето игри в този раздел.
  • Игрите в реално време (стрелба, състезания с коли) също имат цикъл, но той не чака играча. Той се върти 60 пъти в секунда: прочита клавишите, които са натиснати в момента, мести всичко според изминалото време и рисува кадъра. Ако играчът не натисне нищо, светът пак се движи.

Идеята

Същото семе, същата игра

Ако хвърлянията са истински случайни, всяко пускане е различна игра. Как тогава ще провериш, че играта работи? Не знаеш какво да очакваш.

Math.random() не става: на нея не може да се даде начално число. Затова в тези уроци генераторът е малка функция makeRandom(seed), която получава начално число, семе. Тя връща функция, която при всяко извикване дава следващото число от 0 до 1 (без 1), точно като Math.random(). Едно и също семе дава една и съща поредица, всеки път.

makeRandom засега копирай, както ask(). Вътре са побитови операции, които няма да ни трябват. Зарът е Math.floor(random() * 6) + 1: число от 0 до 6 (без 6), отрязано до цяло, плюс 1.

Random first = new Random(42);
Random second = new Random(42);
for (int i = 0; i < 5; i++) {
int a = first.nextInt(6) + 1;
int b = second.nextInt(6) + 1;
System.out.println(a + " и " + b);
}
Изход
3 и 3
4 и 4
1 и 1
3 и 3
1 и 1

Двата генератора с 42 дават едни и същи числа. Пусни програмата пак и ще са същите. Затова примерите и задачите тук имат точен изход, който може да се провери.

Виж изхода и в другите табове. Всеки език има свой генератор: от едно и също семе те дават различни числа. Затова примерите със случайни числа имат изход за всеки език.

По-точно, за любопитните
  • Когато играта е готова за истински играчи, сменяш генератора: const random = Math.random;. Останалият код не се променя, защото и двете се викат по същия начин, random(). Тогава всяка игра е различна.
  • makeRandom не е от стандартната библиотека. В истински проект генератор със семе идва от пакет, например seedrandom.
  • Честа практика е семето да се отпечата в началото на играта. Ако играч намери бъг, с неговото семе можеш да изиграеш точно същата игра.

Идеята

Първо най-малката работеща версия

Програмата от сцената е цяла игра в десетина реда. Тя няма двама играчи, нито меню, нито правила освен „върви напред“. Но върви от началото до края и показва, че цикълът и краят са верни.

Оттам играта расте с по едно правило. Второто правило: фигурката трябва да стигне поле 20 точно. Ако зарът я изпрати по-далеч, тя отскача назад с толкова полета, колкото е прехвърлила. От 17 със зар 5 тя стига 20 и се връща на 18.

Ново правило се добавя, само когато предишното работи. Ако нещо се счупи, знаеш, че е в последното, което си писал.

Идеята

Логиката връща, екранът печата

Правилото „точно 20“ е във функция, която връща новото поле. Краят е функция, която връща true или false:

static int move(int field, int roll) {
int next = field + roll;
if (next > 20) {
return 20 - (next - 20);
}
return next;
}
static boolean isFinished(int field) {
return field == 20;
}
Random random = new Random(7);
int field = 0;
int turn = 0;
while (!isFinished(field)) {
turn++;
int roll = random.nextInt(6) + 1;
field = move(field, roll);
System.out.println("Ход " + turn + ": зар " + roll + ", поле " + field);
}
System.out.println("Точно на 20 след " + turn + " хода");
Изход
Ход 1: зар 5, поле 5
Ход 2: зар 3, поле 8
Ход 3: зар 4, поле 12
Ход 4: зар 5, поле 17
Ход 5: зар 5, поле 18
Ход 6: зар 5, поле 17
Ход 7: зар 5, поле 18
Ход 8: зар 6, поле 16
Ход 9: зар 1, поле 17
Ход 10: зар 1, поле 18
Ход 11: зар 1, поле 19
Ход 12: зар 1, поле 20
Точно на 20 след 12 хода

Нито move, нито isFinished печата нещо. Те само смятат. Печатането е в кода под функциите.

Защо така? Функция, която връща число, можеш да извикаш с каквито стойности искаш и да провериш резултата: move(17, 5) трябва да е 18. Функция, която печата, проверяваш само с очи. А ако утре играта мине от конзолата в прозорец или в браузъра, правилата остават същите, сменя се само показването.

Пробвай

Къде ще стъпи фигурката?

Какво ще отпечата програмата?

function move(field, roll) {
const next = field + roll;
if (next > 20) {
return 20 - (next - 20);
}
return next;
}
function isFinished(field) {
return field === 20;
}
console.log(move(10, 4));
console.log(move(17, 3));
console.log(move(18, 5));
Отговор

Пробвай

Намери грешката

Фигурката трябва да върви напред, а програмата печата поле 0 на всеки ход:

Изход
Ход 1: зар 1, поле 0
Ход 2: зар 3, поле 0
Ход 3: зар 6, поле 0

На кой ред е грешката?

Избери реда с грешката.

Пробвай

Кога свършва играта?

Кое липсва, за да е краят точно на поле 20?

function isFinished(field) {
  return field  20;
}

Упражнение

Три малки игри

Реши задачите в редактора си. Логиката е в функции, които връщат стойност, а печатането е в кода под тях.

1. Надбягване за двама. Ана и Борис хвърлят по ред, с makeRandom(3). Важи правилото „точно 20“ с move от урока. Полетата им са в масив [0, 0]. Който стигне 20 пръв, печели.

Изход
Ана: зар 6, поле 6
Борис: зар 2, поле 2
Ана: зар 2, поле 8
Борис: зар 3, поле 5
Ана: зар 3, поле 11
Борис: зар 1, поле 6
Ана: зар 1, поле 12
Борис: зар 2, поле 8
Ана: зар 3, поле 15
Борис: зар 1, поле 9
Ана: зар 2, поле 17
Борис: зар 4, поле 13
Ана: зар 4, поле 19
Борис: зар 6, поле 19
Ана: зар 1, поле 20
Печели Ана

2. Познай числото. Програмата избира тайно число от 1 до 100 с makeRandom(2026). Играчът пише догадки, по една на ред. След всяка програмата казва „По-голямо е“, „По-малко е“ или „Позна“. Накрая казва от колко опита. Подсказката е функция hint(guess, secret), която връща текста.

Вход
50
75
88
81
84
86
87
Изход
50: По-голямо е
75: По-голямо е
88: По-малко е
81: По-голямо е
84: По-голямо е
86: По-голямо е
87: Позна
Позна от 7 опита

Входът е различен за всеки език, защото генераторите им избират различно тайно число от едно и също семе.

3. Кой спечели морския шах? Първият ред е броят на дъските. Всяка дъска е три реда по три знака: X, O или . за празно поле. Напиши функция winner(board), която връща X, O или ., ако няма три еднакви в ред, колона или диагонал.

Дъската може да е масив от трите прочетени реда: всеки ред е текст, а board[r][c] е знакът на ред r, колона c. Отделен тип за един знак, като char в Java, JavaScript няма: знакът е текст с дължина 1.

Вход
3
XOX
OXO
OOX
XXO
OOO
X.X
XOX
XOO
OXX
Изход
Печели X
Печели O
Няма победител

Още задачи

  • Капани. Поле 13 връща фигурката на поле 5. Добави правилото в move и пусни надбягването пак.
  • Отпечатай семето. Направи семето променлива, отпечатай го в началото и пусни играта с три различни семена.
  • Морски шах за двама. Играчите пишат ред и колона на хода си. След всеки ход покажи дъската и провери с winner дали играта е свършила.

Тест

Провери се

1. Кое е състоянието на надбягването със зар?
2. Защо в играта пише makeRandom(42), а не Math.random?
3. Защо правилото за местене е функция, която връща новото поле, а не функция, която печата?
4. Коя е добрата първа версия на игра?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·