10. Робот Карел¶
Садржај
10.1. Библиотека и директива karel
¶
Библиотека Карел представља Петљино решење познатог метода за прављење почетних корака у учењу програмирања. У програмима се робот Карел, помоћу команди које му корисник задаје, креће кроз мрежу поља где заобилази препреке, сакупља лоптице и оставља их на одређена места.
Ево примера једног таквог задатка, који се решава у браузеру. Задатак корисника је да унесе/модификује Пајтон програм, који се налази у едитору и садржи позиве функција библиотеке Карел.
У овом задатку, робот Карел треба да се помери до краја лавиринта у коме се налази и да покупи све лоптице које се налазе на последњем пољу.
Карел задаци се у Петљадок пројекте укључују помоћу директиве .. karel::
, у коју је потребно сместити Јаваскрипт објекат Карел задатака. Претходни пример добијен је помоћу следећег кода.
.. karel:: Karel_primer1
{
setup: function() {
var world = new World(5, 1);
world.setRobotStartAvenue(1);
world.setRobotStartStreet(1);
world.setRobotStartDirection("E");
world.putBalls(5, 1, 3);
var robot = new Robot();
var code = [
"from karel import *",
"for i in range(?): #zameni znak pitanja odgovarajućim brojem",
" napred()",
"for i in range(3):",
" uzmi()"
]
return {world: world, robot: robot, code: code};
},
isSuccess: function(robot, world) {
return robot.getAvenue() === 5 && robot.getBalls() === 3;
}
}
10.2. Општи облик кода за Карел задатке¶
Тело сваке karel
директиве представља Јаваскрипт објекат који има два метода, setup
и isSuccess
.
Метод
setup
дефинише почетни изглед Карел проблема, тако што враћа објекат који у себи садржи објекте свет, робот и кôд. Помоћу ова три објекта је у потпуности описана почетна ситуација сваког Карел проблема.Метод
isSuccess
проверава да ли стање проблема које му је прослеђено представља решење проблема. Овај метод као аргументе добија објектеrobot
иworld
, а враћа логичку вредност која говори да ли се дати робот и свет налазе у циљном стању, које је описано поставком проблема. Циљно стање не мора да буде јединствено.
У претходном примеру, да би стање било прихваћено као циљно, потребно је да се Карел налази на захтеваној позицији и да има код себе све три лоптице.
У различитим Карел проблемима ови услови могу да буду другачији (нпр. да Карел држи само неке, а не све лоптице које постоје у задатку, или да се налази на некој другој позицији, или да је на одређено поље оставио тражени број лоптица).
10.3. Задавање Кареловог света¶
Објекат world
представља таблу по којој се Карел креће. Овом објекту у методу setup
задајемо димензије (број редова и колона), положај и оријентацију робота, распоред препрека, лоптица и рупа. Сама табла се састоји од авенија (вертикалних линија) и улица (хоризонталних линија).
Димензије света (тј. табле) задајемо при позиву конструктора var world = new World(x, y);
, где је x
број авенија (број вертикалних линија, ширина табле), а y
број улица (број хоризонталних линија, висина табле).
Свако поље табле је пресек једне хоризонталне и једне вертикалне линије. Према томе, поље је одређено редним бројем авеније (координата x
) и редним бројем улице (координата y
). Координате, односно авеније и улице, броје се од 1.
10.3.1. Постављање робота у почетни положај¶
Позицију и оријентацију робота задајемо у методу setup
помоћу следећих метода:
world.setRobotStartAvenue(x0)
- поставља робота на вертикалу (авенију) са редним бројемx0
world.setRobotStartStreet(y0)
- поставља робота на хоризонталу (улицу) са редним бројемy0
Подсећамо да се координате (авеније и улице) броје од 1.
world.setRobotStartDirection(smer)
- задаје почетну оријентацију робота. Аргументsmer
је стринг, који треба да има једну од вредностиN
(North, север),S
(South, југ),W
(West, запад) илиE
(East, исток). Вредност аргумента означава смер у коме желимо да иницијално окренемо робота.
У следећем примеру Карел задатка детаљније су коментарисане наредбе у методима setup
и isSuccess
.
.. karel:: Karel_primer2
{
setup: function() {
var world = new World(5, 4); // definišemo veličinu table
world.setRobotStartAvenue(1); // robota postavljamo na prvu vertikalu
world.setRobotStartStreet(2); // robota postavljamo na drugu horizontalu
world.setRobotStartDirection("S"); // robot je inicijalno okrenut ka jugu (S = South)
world.putBall(5, 1); // postavljamo lopticu na polje (x, y) = (5, 1)
var robot = new Robot();
var code = [
"from karel import *",
"napred()",
"levo()",
"for i in range(?): #zameni znak pitanja odgovarajućim brojem",
" napred()",
"uzmi()"
]
return {world: world, robot: robot, code: code};
},
isSuccess: function(robot, world) {
// Na kraju, robot treba da bude na polju (x, y) = (5, 1) i da ima jednu lopticu
return robot.getStreet() === 1 && robot.getAvenue() === 5 && robot.getBalls() === 1;
}
}
Кôд који смо представили ће у браузеру дати овакав задатак:
Ако корисник напише кôд тако да Робот покуша да изађе са табле, програм се прекида уз поруку о грешци „Robot went outside of the grid!”, која значи да је робот изашао са табле. Проверите ово у следећем примеру:
Слично би се догодило ако би робот покушао да прође кроз унутрашњу препреку, да узме лоптицу на пољу на ком нема лоптица, или да остави лоптицу када нема лоптица код себе. Корисник (решавач задатка) треба да води рачуна да током извршавања његовог Пајтон програма робот не уради не једну од ових ствари.
10.3.2. Постављање лоптица и рупа¶
Карелов задатак најчешће је да лоптице које се налазе на табли сакупља, помера и спушта на одређена поља. Почетни положај ових лоптица задаје се у методу setup
, помоћу следећих наредби:
world.putBalls(x, y, n)
- на поље (x
,y
) ставиn
лоптицаworld.putBall(x, y)
- на поље (x
,y
) стави једну лоптицу.
Слично томе, Карел често треба да убаци лоптице у рупе на одређеном пољу табле. Распоред рупа се такође задаје у методу setup
, а за то служе ове наредбе:
world.putHoles(x, y, n)
- на поље (x
,y
) ставиn
рупа.world.putHole(x, y)
- на поље (x
,y
) стави једну рупу.
Подразумевамо да свака рупа може да прими само једну лоптицу. Када Карел на неко поље остави онолико лоптица колико је на том пољу било рупа, рупе нестају са табле и дато поље постаје обично поље. Ако би Карел наставио да оставља лоптице на на том пољу, оне би на том пољу остале и Карел би касније могао да их покупи.
У следећем, решеном примеру, Карел треба да узме све лоптице, да попуни рупе трима лоптицама и задржи вишак лоптица.
Ево како изгледа кôд овог примера:
.. karel:: Карел_узми_све_лоптице_напуни_рупе
:blockly:
{
setup: function() {
var world = new World(4, 1);
world.setRobotStartAvenue(4);
world.setRobotStartStreet(1);
world.setRobotStartDirection("W");
world.putBalls(3, 1, 5); // stavi 5 loptica na polje (3, 1)
world.putHoles(2, 1, 3); // stavi rupu "dubine" 3 na polje (2, 1)
var robot = new Robot();
var code = [
"from karel import *",
"napred()",
"for i in range(5):",
" uzmi()",
"napred()",
"for i in range(3):",
" ostavi()",
"napred()",
]
return {world: world, robot: robot, code: code};
},
isSuccess: function(robot, world) {
for (var i = 1; i <= world.getAvenues(); i++)
for (var j = 1; j <= world.getStreets(); j++)
if (world.getBalls(i, j) != 0)
return false;
return true;
}
}
10.3.3. Постављање зидова¶
На табли се могу нацртати и зидови кроз које карел не може да прође. Они се на таблу доцртавају помоћу два метода објекта world
:
addEWWall(x,y,d)
- исцртава хоризонталан (EW = East-West) зид. Зид почиње изнад поља(x,y)
и продужава се на десно, тако да наткриваd
поља.addNSWall(x,y,d)
- исцртава вертикалан (NS = North-South) зид. Зид почиње десно од поља(x,y)
и продужава се на горе, са десне стране укупноd
поља.
У следећем примеру можемо да видимо како изгледа коришћење ових метода и прављење зидова.
.. karel:: Zidovi_primer
{
setup:function() {
var world = new World(7,5);
world.setRobotStartAvenue(1);
world.setRobotStartStreet(3);
world.setRobotStartDirection("E");
world.putBall(2, 3);
world.addNSWall(1, 2, 3); // vertikalan zid dužine 3, od polja (1, 2)
world.addEWWall(2, 2, 3); // horizontalan zid dužine 3, od polja (2, 2)
world.addEWWall(2, 3, 4); // horizontalan zid dužine 4, od polja (2, 3)
var robot = new Robot();
var code = [
"from karel import *",
];
return {robot:robot, world:world, code:code};
},
isSuccess: function(robot, world) {
return robot.getBalls() === 1;
},
}
Кôд који смо представили ће у браузеру дати овакав задатак:
Проверите шта се дешава ако је прва наредба „решења” наредба napred()
.
Програм се прекида уз поруку о грешци „Robot crashed at the wall!”, тј. „Робот се разбио о зид!”
10.3.4. Рандомизација Кареловог света¶
Релативно често понашање корисника курса или приручника је да, након што научи основно управљање роботом, одбија да научи нпр. наредбу while
јер није једнако мотивисан за то. Наиме, корисник увиђа да сваки задатак може да реши пишући већи број наредби и не користећи петљу. Због тога је пожељно да се у појединим задацима у Карелов свет уведе фактор случајности. То значи да лавиринт у коме се Карел креће неће при сваком покретању програма да изгледа исто.
На пример, у следећем задатку Карел треба да се креће напред док год може, а затим да узме лоптицу. Корисник не може да реши задатак тако што преброји поља и упише потребан број наредби napred()
, јер се број поља мења од извршавања до извршавања. Испробајте ово кликом на дугме Врати на почетак
или на дугме Покрени програм
.
На овај начин, корисник је и даље мотивисан да савлада нову лекцију (нпр. научи наредбу while
), јер без тога не може да реши задатак.
Ефекат случајности се врло лако додаје у опис проблема помоћу функције Math.random
. Следи кôд којим је задат претходни проблем.
.. karel:: Randomizacija_primer
:blockly:
{
setup:function() {
function random(n) {
return Math.floor(n * Math.random());
}
var N = 2 + random(8);
var world = new World(N, 1);
world.setRobotStartAvenue(1);
world.setRobotStartStreet(1);
world.setRobotStartDirection("E");
world.putBall(N, 1);
var robot = new Robot();
var code = ["from karel import *",
"while moze_napred():",
" napred()",
"uzmi()"];
return {robot:robot, world:world, code:code};
},
isSuccess: function(robot, world) {
return robot.getBalls() === 1;
}
}
10.4. Још један начин задавања Кареловог света¶
Састављачу задатака (аутору) може да буде лакше да Карелов свет „нацрта” у коду. Прецизније, Карелов свет може да се зада помоћу низа стрингова на следећи начин:
.. karel:: Zadavanje_nizom_stringova_primer
:blockly:
{
setup:function() {
var w = [
'███████',
'█0█1█1█',
'█.█.█.█',
'█0.0.0█',
'█.█.█.█',
'█W█1█1█',
'███████'
];
var ny = Math.floor(w.length / 2);
var nx = Math.floor(w[0].length / 2);
var world = new World(nx, ny);
for (let y = 1; y <= ny; y++) {
let wy = 2*(ny-y) + 1;
for (let x = 1; x <= nx; x++) {
let wx = 2*x - 1;
if (y < ny && w[wy - 1].charAt(wx) == "█") world.addEWWall(x, y, 1);
if (x < nx && w[wy].charAt(wx + 1) == "█") world.addNSWall(x, y, 1);
let c = w[wy].charAt(wx);
let pos = "SWEN".indexOf(c);
if (pos > -1) {
world.setRobotStartAvenue(x);
world.setRobotStartStreet(y);
world.setRobotStartDirection("SWEN".charAt(pos));
}
let d = w[wy].charCodeAt(wx);
if (d >= 48 && d < 58) world.putBalls(x, y, d - 48);
}
}
var robot = new Robot();
var code = ["from karel import *",
"# unesi naredbe"];
return {robot:robot, world:world, code:code};
},
isSuccess: function(robot, world) {
return robot.getBalls() == 4;
}
}
Када се Карелов свет задаје овако, кôд у наставку функције setup
(након дефинисања низа стрингова w
) је исти у сваком задатку и може да се копира у друге задатке. У низу стрингова w
, бројеви представљају број лоптица на пољу, карактери █
представљају препреке, а једно од слова SWEN
(само једно слово сме да се појави у подацима) представља локацију, а уједно и оријентацију робота Карела. Ограничења оваквог начина задавања су то што не могу да се поставе лоптице на поље са ког полази Карел, а не могу ни да се задају рупе. У случају потребе, кôд у функцији setup
испод низа w
може и да се модификује и да се тиме отклоне ова ограничења.
Претходно наведеним кôдом се добија овај задатак:
Ако желимо да буде случајно изабран један од оваквих, „сликовних” описа задатка, можемо да задамо неколико лавирината и пустимо функцију да одабере један. На пример, ако је задатак Карела да открије на којем од њему суседних поља се налази једина лоптица у лавиринту и да је узме, онда можемо да задамо неколико (овде три) различита лавиринта са једном лоптицом на неком од суседних поља, и да насумице изаберемо један од њих. Ево како то може да се уради у кôду:
.. karel:: Zadavanje_nizom_stringova_primer
:blockly:
{
setup:function() {
function random(n) {
return Math.floor(n * Math.random());
}
var ww = [
[
'█████',
'█0.0█',
'███.█',
'█1.N█',
'███.█',
'█0.0█',
'█████'
],
[
'█████',
'█1.0█',
'█...█',
'█E.0█',
'█████'
],
[
'███████',
'█0█0█0█',
'█.█.█.█',
'█0.W.0█',
'█.█.█.█',
'█0█1█0█',
'███████'
]
];
let choice = random(ww.length);
var w = ww[choice];
...
Надаље функција setup
исто као у претходном примеру, па је овде не понављамо.
10.5. Променљива code
¶
Променљива code
представља листу стрингова који садрже линије Пајтон кода, које ће се кориснику у почетном стању Карел задатка појавити у едитору.
Наредбе које се могу задати Карелу представљају Пајтон функције из библиотеке Карел. На располагању су нам следеће функције за задавање и решавање проблема:
napred()
- помери се једно поље напред,levo()
- окрени се 90 степени налево (у смеру супротном казаљки на сату),desno()
- окрени се 90 степени надесно (у смеру казаљке на сату),uzmi()
- покупи лоптицу са поља на којем се налазиш,ostavi()
- спусти лоптицу на поље на којем се налазишmoze_napred()
- проверава да робот може да се помери напред (да испред њега не постоји зид или крај табле),ima_loptica_na_polju()
- проверава да ли на пољу на ком се робот налази има лоптица,broj_loptica_na_polju()
- враћа број лоптица на пољу на ком се робот налази,ima_loptica_kod_sebe()
- проверава да ли робот тренутно има лоптица код себе.broj_loptica_kod_sebe()
- враћа број лоптица које робот тренутно има код себе,izgovori(str)
- шаље кориснику поруку у модалном прозору. Аргумент метода је стринг који садржи поруку.
10.6. Провера успеха - подаци о роботу и свету¶
Да бисмо могли да проверимо да ли је корисник успешно решио задатак, потребни су нам методи помоћу којих у методу isSuccess
можемо да испитујемо стање робота и света - табле. За то користимо следеће методе:
методи робота:
robot.getAvenue()
, враћа редни број авеније (вертикале) у којој се робот налазиrobot.getStreet()
, враћа редни број улице (хоризонтале) у којој се робот налазиrobot.getBalls()
, враћа број лоптица које робот има код себеrobot.lastMessage()
, враћа последњу поруку коју је робот изговорио.
Помоћу метода robot.lastMessage()
можемо да у методу isSuccess
проверимо да ли је робот изговорио оно што је требало да изговори.
методи света:
world.getAvenues()
, враћа број авенија (вертикала) које постоје у овом светуworld.getStreets()
, враћа број улица (хоризонтала) које постоје у овом светуworld.getBalls(x, y)
, враћа број лоптица на пољу(x, y)
. Ако на пољу има непопуњених рупа, овај метод враћа негативан број.world.getHoles(x, y)
, враћа број рупа на пољу(x, y)
.world.getRobotStartAvenue()
, враћа почетну авенију (вертикалу) роботаworld.getRobotStartStreet()
, враћа почетну улицу (хоризонталу) роботаworld.getRobotStartBalls()
, враћа број лоптица које су на почетку код робота
Методе world.getAvenues()
и world.getStreets()
обично користимо као границе петљи у методу isSuccess
, ако је потребно да испитамо свако поље табле (а величина света није константа).
Наравно, све наведене методе можемо да употребљавамо и у методу setup
(мада за тиме најчешће нема потребе).
У следећем, решеном примеру робот треба да преброји лоптице и изговори њихов број (не узевши ни једну од њих).
Овако изгледа кôд примера са пребројавањем:
.. karel:: карел_сабира
{
setup: function() {
function random(n) {
return Math.floor(n * Math.random());
}
var world = new World(6, 1);
world.setRobotStartAvenue(1);
world.setRobotStartStreet(1);
world.setRobotStartDirection("E");
for (var k = 2; k <= 6; k++)
world.putBalls(k, 1, random(10));
var robot = new Robot();
robot.setInfiniteBalls(true);
var code = [
"from karel import *",
"broj = 0",
"while moze_napred():",
" napred()",
" broj += broj_loptica_na_polju()",
"izgovori(broj)"
];
return {world: world, robot: robot, code: code};
},
isSuccess: function(robot, world) {
if (robot.getBalls() > 0)
return false;
var broj = 0;
for (var k = 2; k <= 6; k++)
broj += world.getBalls(k, 1);
return broj == parseInt(robot.lastMessage);
}
}
10.7. Блокли¶
Пајтон наредбе које се налазе у едитору Карел задатка могу да се представе и у виду графичких блокова, сличних оним у окружењу Скреч или Мејк-код. Ово може да буде од велике помоћи почетницима у програмирању, којима је тешко да куцају наредбе, поготово ако су имали искуства са поменутим сличним окружењима. Корисник може у било ком требутку да изабере режим писаних Пајтон наредби или режим блокова који се превлаче и ређају мишем, као и да неограничено прелази из једног режима у други и назад.
Ова могућност се укључује писањем опције :blockly: испод директиве .. karel::
. Када се употреби опција :blockly:
, поред дугмади „Покрени програм” и „Врати на почетак” добија се и дугме „Blockly”. Кликом на ово дугме, у модалном прозору добиће се нови едитор који омогућује да се код пише ређањем блокова.
Када се у оквиру блоклијевог едитора кликне на дугме врати у Пајтон
, све промене које су урађене у блоклију биће ажуриране и у едитору кода Карел проблема.
Кôд за претходни проблем изгледа овако:
.. karel:: Blokli_primer
:blockly:
{
setup: function() {
var world = new World(4, 1);
world.setRobotStartAvenue(1);
world.setRobotStartStreet(1);
world.setRobotStartDirection("E");
for (var k = 2; k <= 4; k++)
if (Math.random() > 0.5)
world.putBall(k, 1);
var robot = new Robot();
var code = [
"from karel import *",
"napred(); # idi napred",
"if ima_loptica_na_polju(): # ako je na polju loptica:",
" uzmi() # uzmi lopticu",
"",
"napred(); # idi napred",
"if ima_loptica_na_polju(): # ako je na polju loptica:",
" uzmi() # uzmi lopticu",
"",
"# dopuni program",
"???"
]
return {world: world, robot: robot, code: code};
},
isSuccess: function(robot, world) {
return world.getBalls(2, 1) == 0 &&
world.getBalls(3, 1) == 0 &&
world.getBalls(4, 1) == 0;
}
}
10.8. Подсетник¶
Директива
.. karel::
- Карел задаци се у Петљадок пројекте додају помоћу ове директиве. Тело директиве је Јаваскрипт објекат који садржи поставку Карел задатка.Опција
:blockly:
(блокли) - опција директиве Карел (без аргумената), омогућава едитовање решења уз помоћ блокова.Метод
setup
- помоћу њега се задаје Карелов свет, креира и враћа објектеworld
,robot
иcode
.Објекат
world
- објекат који представља таблу задате величине. Преко њега се поставља почетни положај робота, распоред зидова, лоптица и рупа.Објекат
robot
- објекат који чува податке о томе где се налази робот, колико има лоптица и шта изговара.code
- варијабла у коју се смешта листа стрингова - Пајтон наредбе које корисник добија на почетку решавањаworld.addEWWall(x1, x1, d)
- метод који додаје хоризонтални зид. Као аргументе узима координате почетне тачке и дужину зидаworld.addNSWall(x1, x1, d)
- метод који додаје вертикални зид. Као аргументе узима координате почетне тачке и дужину зидаworld.setRobotStartAvenue(x)
- задаје почетну вертикалу роботаworld.setRobotStartStreet(y)
- задаје почетну хоризонталу роботаworld.setRobotStartDirection(smer)
- задаје почетну оријентацију робота. Аргумент је једна од вредности „Е”, „W”, „S”, „N”, што је ознака стране света на енглеском.world.putBall(x, y)
- смешта лоптицу на одређено поље, као аргументе узима x,y координате поља.world.putBalls(x, y, n)
- смешта више лоптица на одређено поље, као аргументе узима x,y координате поља и број лоптицаworld.putHole(x, y)
- смешта рупу на одређено поље, као аргументе узима x,y координате пољаworld.putHoles(x, y, n)
- смешта више рупа на одређено поље, као аргументе узима x,y координате поља и број рупаWorld(x,y)
- конструктор објектаworld
. Као аргументе узима два цела броја који представљају број хоризонталних и вертикалних линија на мапи.world.RobotStartBalls
- број лоптица са којима карел почињеisSuccess
- метод који дефинише услове које по извршењу програма корисника треба да испуњавају свет и робот, да би се задатак сматрао решеним.world.getRobotStartAvenue
- метод који враћа број почетне авеније Роботаworld.getRobotStartStreet
- метод који враћа број почетне улице Роботаworld.getAvenues
- метод који враћа број авенија на таблиworld.getStreets
- метод који враћа број улица на таблиworld.getBalls(x,y)
- метод који враћа број лоптица на одређеном пољу. Као координате узима број авеније и број улице жељеног пољаworld.getHoles(x,y)
- метод који враћа број рупа на одређеном пољу. Као координате узима број авеније и број улице жељеног пољаrobot.getBalls()
- метод који враћа број лоптица које се налазе код роботаrobot.getAvenue()
- метод који враћа авенију у којој се робот налазиrobot.getStreet()
- метод који враћа улицу у којој се робот налази