diff --git a/problem-set/FillRaw.md b/problem-set/FillRaw.md deleted file mode 100644 index 792b9f3..0000000 --- a/problem-set/FillRaw.md +++ /dev/null @@ -1,105 +0,0 @@ -# FillRaw - -პრობლემა: -``` -კარელი დგას 1x1 უჯრაზე, შეავსებინეთ მას პირველი ქუჩა ბრილიანტებით. ანუ პირველი ქუჩის ყველა უჯრაზე უნდა იდოს ზუსტად ერთი ბრილიანტი. ჩათვალეთ რომ საწყის სამყაროში ბრილიანტები არსად არ დევს. -``` - - - -## პრობლემის გადაჭრის გზა -პირველ რიგში ჩვენი პრობლემა დავყოთ ორ ნაწილად -* კარელმა უნდა გაიაროს მთლიანი ქუჩა -* სიარულთან ერთად, კარელმა პარალელურად უნდა განათავსოს ბრილიანტები ქუჩაზე - ---- - -### მთლიანი ქუჩის გავლის პრობლემას გადავჭრით შემდეგი საშაულებით -1. გამოვიყენოთ კარელისათვის ცნობილი `frontIsClear()` მეთოდი. -2. გამოვიყენოთ `while` ციკლი. -3. წინაღობის შემოწმების შემდგომ, განვახორციელოთ 1 უჯრით წინ გადანაცვლება `move()` მეთოდის საშუალებით, რათა შევძლოთ მთლიანი ქუჩის გავლა. - - -შესაბამისად, მივიღებთ კოდს: -```java -while(frontIsClear()) { - move(); -} -``` - -> *როდესაც კარელის წინ აღმოჩნდება კედელი, `frontIsClear()` მეთოდი `while` ციკლს შეატყობინებს, რომ კარელის წინ დაბრკოლებაა და კარელი შეწყეტს სვლას (ციკლი დასრულდება).* - ---- - -### სიარულის პარალელურად, ბრილიანტების განთავსება ქუჩაზე -ახლა, როდესაც კარელი წარმატებით გადის ქუჩას, მნიშვნელოვანია ბრილინატების განთავსება, რომელსაც `putBeeper()` მეთოდის საშუალებით გავაკეთებთ. მისი `while` ციკლში მოთავსებით კოდი მიიღებს შემდეგ სახეს -```java -while(frontIsClear()) { - putBeeper(); - move(); -} -``` -ამ ეტაპისთვის გვგონია, რომ პრობლემა გადაჭრილია და არანაირ მოდიფიკაციას არ საჭიროებს, თუმცა კოდის წარმატებით გაშვების შემდგომ მივიღებთ სამყაროს, სადაც კარელმა წარმატებით შეძლო მისი ქუჩის უჯრებზე მხოლოდ 1 ბრილიანტის განთავსება, **გარდა 1 უჯრისა.** - -ეს უჯრა, სწორედ ისაა, რომელზედაც იგი მუშაობის დასასრულს იმყოფება. მოდით, განვიხილოთ თუ რას შეიძლებოდა გამოეწვია მსგავსი სახის პრობლემა. -* ამ შეცდომას პროგრამირებაში [Off-By-One Error](https://stackoverflow.com/questions/2939869/what-is-an-off-by-one-error-and-how-do-i-fix-it)-ად მოიხსენიებენ და თავისი სახელიდან გამომდინარე მივხვდებით, რომ შეცდომა ძირითადად **იტერირების** დროს შეიძლება წარმოიშვას. -* განვიხილოთ კარელის შემთხვევა (სამყარო 10x10-ზე) - * კარელი დგას მე-9 უჯრაზე. - * დავდეთ ბრილიანტი. - * გამოვიძახეთ `move()` მეთოდი და გადავინაცვლეთ მე-10 უჯრაზე. - * მე-10 უჯრაზე გადასვლის შემდგომ კარელი ამოწმებს, აქვს თუ არა მას წინაღობა. იგი იძახებს`frontIsClear()` მეთოდს, რაზეც იგი იღებს პასუხს, რომ მის წინ მდებარეობს კედელი. შესაბამისად, კარელი ასრულებს `while` ციკლს და ვეღარ ახერხებს `putBeeper()` მეთოდის გამოძახებას. - -![Image Of Karel Standing on 10th Box](/problem-set/images/XQRF1oc.png) - -ამ ყველაფრის შემდგომ, პრობლემის გადასაჭრელად, დაგვჭირდება `putBeeper()` მეთოდის ჩამატება ჩვენს კოდში: -```java -while(frontIsClear()) { - putBeeper(); - move(); -} -// დებს ბრილიანტს, ბოლო უჯრაზე -putBeeper(); -``` ---- - -## რატომ იმუშავებს კოდი ნებისმიერი სამყაროს ზომისათვის? -* განვიხილოთ სამყარო, რომლის სიგრძეც 1-ის ტოლია (სიმაღლეს მნიშვნელობა არ აქვს, ვინაიდან კარელი მოძრაობს მარცხნიდან მარჯვნივ, მხოლოდ). - * ჩვენს მიერ დაწერილი კოდი, პირველ რიგში შეამოწმებს, არის თუ არა კარელის წინ კედელი. - * `frontIsClear()` მეთოდი დააბრუნებს პასუხს, რომ კარელს გზა დაბლოკილი აქვს და ვერ შეძლებს წინ წასვლას, შესაბამისად კარელი გამოტოვებს `while` ციკლს - * კარელი გადაინაცვლებს `while` ციკლის შემდგომ ბრძანებაზე და დახვდება `putBeeper()` მეთოდი, რაც უზრუნველყოფს ბრილიანტის წარმატებით დადებას სამყაროში. შესაბამისად, ჩვენი პრობლემაც გადაჭრილია და კარელი იდგება ქუჩაზე, რომლის ყველა უჯრაზე მხოლოდ 1 ბრილიანტი დევს - - ![Karel in 1x10 World](/problem-set/images/RwZB99V.png) - -* ნებისმიერი სხვა სამყაროსთვის, რომლის სიგრძეც > 1, ზუსტად იგივე ლოგიკით იმუშავებს, როგორც ზემოთხსენებული 10x10 სამყაროზე იმიტომ, რომ კარელი ამოწმებს წინ არსებულ დაბრკოლებას და მხოლოდ ამის შემდგომ იღებს გადაწყვეტილებას, გააგრძელოს თუ დაასრულოს სვლა. - ---- - -## შესაძლო ხარვეზები ამოხსნის იმპლემენტაციისას -პირველ რიგში, მინდა მოგილოცოთ პროგრამის წარმატებით დასრულება და იმპლემენტაცია, თუმცა არსებობს 2 დეტალი, რისი გათვალისწინებაც საკმაოდ მნიშვნელოვანია ამ ამოცანის გადაჭრისას - -1. გასათვალისწინებელია ზემოთ ნახსენები [Off-By-One Error](https://stackoverflow.com/questions/2939869/what-is-an-off-by-one-error-and-how-do-i-fix-it) -2. ასევე, საინტერესოა, რა მოხდება თუ `while` ციკლის ტანში ადგილებს გავუცვლით `move()`-სა და `putBeeper()` მეთოდებს. ერთი შეხედვით თითქოს ისევ სწორად უნდა იმუშაოს პროგრამამ, ვინაიდან ჩვენ მხოლოდ 2 ბრძანებას გავუცვალეთ ადგილი. - 1. მივიღებთ კოდს - ```java - while(frontIsClear()) { - move(); - putBeeper(); - } - putBeeper(); - ``` - 2. თუ კარგად დავაკვირდებით და გავიაზრებთ კოდს, შევამჩნევთ, რომ კარელის ქმედებები შეიცვლება. - * კოდის გაშვების შემდეგ კარელი ჯერ გადაინაცვლებს მეორე უჯრაზე, ხოლო ამის შემდეგ დადებს ბრილიანტს. - * თავისთავად ჩვენს კოდში წარმოიშვა ხარვეზი, რაც მოიაზრებს შემდეგს: კარელის სამყაროში პირველი უჯრა ყოველთვის ცარიელი დარჩება, ხოლო დამთავრების ადგილას იგივე განათავსებს 2 ბრილინატს. - - ![Karel in 1x10 World](/problem-set/images/karel_bug_0.png) - - 3. ამ პრობლემას 1 მარტივი გადაჭრის გზა აქვს. - 1. ავიტანოთ `while` ციკლის გარეთ მყოფი `putBeeper()` მეთოდი და გამოვიძახოთ `while` ციკლის გამოძახებამდე. - 2. ახალი კოდი - ```java - putBeeper(); - while(frontIsClear()) { - move(); - putBeeper(); - } - ``` diff --git a/problem-set/house.java b/problem-set/house.java new file mode 100644 index 0000000..309ff95 --- /dev/null +++ b/problem-set/house.java @@ -0,0 +1,76 @@ +import java.awt.Color; + +import acm.graphics.GLine; +import acm.graphics.GOval; +import acm.graphics.GRect; +import acm.program.GraphicsProgram; + +public class house extends GraphicsProgram { + + private static final int WALL_WIDTH = 300; + private static final int WALL_HEIGHT = 300; + private static final int ROOF_HEIGHT = 100; + private static final int HANDLE_OFFSET = 15; + private static final int HANDLE_SIZE = 5; + private static final Color WALL_CLR = Color.GRAY; + private static final Color ROOF_CLR = Color.BLACK; + private static final Color WINDOW_CLR = Color.YELLOW; + private static final Color DOOR_CLR = Color.DARK_GRAY; + private static final Color HANDLE_CLR = Color.BLACK; + + public void run() { + drawHouse(); + } + + private void drawHouse() { + drawWall(); + drawWindows(); + drawDoor(); + drawRoof(); + } + + private void drawWall() { + GRect wall = new GRect(getWidth() / 2 - WALL_WIDTH / 2, getHeight() - WALL_HEIGHT, WALL_WIDTH, WALL_HEIGHT); + wall.setFilled(true); + wall.setColor(WALL_CLR); + add(wall); + } + + private void drawWindows() { + GRect window1 = new GRect(getWidth() / 2 - 3 * WALL_WIDTH / 8, getHeight() - 2 * WALL_HEIGHT / 3, + WALL_WIDTH / 4, WALL_HEIGHT / 5); + window1.setFilled(true); + window1.setFillColor(WINDOW_CLR); + add(window1); + GRect window2 = new GRect(getWidth() / 2 + WALL_WIDTH / 8, getHeight() - 2 * WALL_HEIGHT / 3, WALL_WIDTH / 4, + WALL_HEIGHT / 5); + window2.setFilled(true); + window2.setFillColor(WINDOW_CLR); + add(window2); + } + + private void drawDoor() { + GRect door = new GRect(getWidth() / 2 - WALL_WIDTH / 8, getHeight() - WALL_HEIGHT / 3, WALL_WIDTH / 4, + WALL_HEIGHT / 3); + door.setFilled(true); + door.setColor(DOOR_CLR); + add(door); + GOval handle = new GOval(getWidth() / 2 - WALL_WIDTH / 8 + HANDLE_OFFSET, + getHeight() - WALL_HEIGHT / 3 + 2 * HANDLE_OFFSET, HANDLE_SIZE, HANDLE_SIZE); + handle.setFilled(true); + handle.setColor(HANDLE_CLR); + add(handle); + } + + private void drawRoof() { + GLine line1 = new GLine(getWidth() / 2 - WALL_WIDTH / 2, getHeight() - WALL_HEIGHT, getWidth() / 2, + getHeight() - WALL_HEIGHT - ROOF_HEIGHT); + line1.setColor(ROOF_CLR); + add(line1); + GLine line2 = new GLine(getWidth() / 2, getHeight() - WALL_HEIGHT - ROOF_HEIGHT, + getWidth() / 2 + WALL_WIDTH / 2, getHeight() - WALL_HEIGHT); + line1.setColor(ROOF_CLR); + add(line2); + } + +} \ No newline at end of file diff --git a/problem-set/houseExp.md b/problem-set/houseExp.md new file mode 100644 index 0000000..ac8e698 --- /dev/null +++ b/problem-set/houseExp.md @@ -0,0 +1,87 @@ +# house + +## პრობლემა: +გრაფიკული პროგრამის გამოყენებით დახატეთ სახლი. + +## ამოხსნა: +პირველ რიგში ჩვენი პრობლემა დავყოთ რამდენიმე ნაწილად: +* დავხატოთ კონტური (კედლები) +* დავხატოთ ფანჯრები +* დავხატოთ კარი +* დავხატოთ სახურავი + +--- + +### მომზადება პრობლემის გადასაჭრელად +გრაფიკულ პრობლემებში მუდმივად გვჭირდება მუდმივი ცვლადები, რომლებიც საგნების ზომას(მაგალითად, + ჩვენს შემთხვევაში, სახლის სიმაღლე, სახურავის სიმაღლე, კედლების ფერი...) განსაზღვრავენ. +ამიტომ, სანამ უშუალოდ კოდის წერას დავიწყებთ, კარგი იქნება თუ ამ მუდმივ ცვლადებს ცალკე აღვწერთ +private static final ის დახმარებით. ეს საშუალებას მოგვცემს სურვილის შემთხვევაში მარტივად შევცვალოთ განზომილებები. + +### ნახატის სიმეტრიულობის პრობლემის გადაჭრის გზა +უკეთესია თუ ნებისმიერი ზომის კანვასზე სახლი სიმეტრიულად, შუაში დაიხატება. ამ პრობლემის +გადაჭრა შესაძლებელია ფუნქციით getWidth() და getHeight(). პირველი აბრუნებს კანვასის სიგანეს (x), +ხოლო მეორე კი მის სიმაღლეს(y). ამის საშუალებით ჩვენ შეგვიძლია ყოველთვის კანვასის შუაში დავხატოთ სახლის კონტური. +ამ შემთხვევაში მე გადავწვიტე, რომ სახლის სიგანე 8 ნაწილად დავყო, სიმაღლე კი 3-ად. შედეგად, ყოველთვის +სახლის ზომების პროპორციულად შემეძლება ობიექტების ჩამატება. + +> *GRect ობიექტს 4 ცვლადის გადაცემის შემთხვევაში ჯერ გადაეცემა x კოორდინატი, შემდეგ y, ხოლო ამის შემდეგ სიგანე და სიგრძე. 2 ცვლადის გადაცემის შემთხვევაში კი ჯერ სიგანე უნდა გადავცეთ, შემდეგ სიმაღლე და ობიექტი კანვასის სათავეში შეიქმნება.* + +> *გავითვალისწინოთ, რომ კანვასში სათავედ ითვლება ზედა მარცხენა კუთხე. x კოორდინატი ითვლება მარცხნიდან მარჯვნივ,ხოლო y ზემოდან ქვემოთ.* + +მაგალითად, კედლების კოორდინატების არჩევის შემთხვევაში მივიღებთ კოდს: +```java + GRect wall = new GRect(getWidth()/2 - WALL_WIDTH/2, getHeight() - WALL_HEIGHT, WALL_WIDTH, WALL_HEIGHT); +``` + +ხოლო ფანჯრის კოორდინატის არჩევის შემთხვევაში შემდეგს: +```java +GRect window1 = new GRect(getWidth() / 2 - 3*WALL_WIDTH/8, getHeight() - 2*WALL_HEIGHT/3, WALL_WIDTH/4, WALL_HEIGHT / 5); + GRect window2 = new GRect(getWidth() / 2 + WALL_WIDTH/8, getHeight() - 2*WALL_HEIGHT/3, WALL_WIDTH/4, WALL_HEIGHT / 5); +``` +> *პირველი ფანჯრის x კოორდინატად ვიღებ სახლის სიგანის პირველ მერვედს, ხოლო მეორე ფანჯრისთვის 5/8-ს. y კოორდინატად კი ორივესთვის სახლის სიმაღლის პირველი მესამედი ავარჩიე* + +--- + +### სახლის კომპონენტების დახატვა +როგორც ზემოთ აღვწერეთ, უკვე მარტივად შეგვიძლია კანვასის ზომების დამოუკიდებლად, სახლის კომპონენტებისთვის +კოორდინატების არჩევა. კარის x კოორდინატად გადავწვიტე ამეღო სახლის სიგრძის პირველი 3/8-დი. სიგანე იყოს +სახლის სიგანის მეოთხედი, სიმაღლე კი სახლის სიმაღლის მესამედი. +მივიღებთ კოდს: +```java + GRect door = new GRect(getWidth() / 2 - WALL_WIDTH/8, getHeight() - WALL_HEIGHT/3, WALL_WIDTH/4, WALL_HEIGHT/3); + ``` + + სახელურის კოორდინატები კარის კოორდინატებზე დამოკიდებული იყოს. მისი x კოორდინატი კარის ამავე კოორდინატისგან + დაშორებული იყოს `HANDLE_OFFSET` ზომით, რომელიც დასაწყისში უკვე აღვწერეთ. სახელურის ფორმა ოვალი იყოს და რადიუსიც + დავაფიქსიროთ `HANDLE_SIZE` ცვლადით. + + მივიღებთ კოდს: + ```java + GOval handle = new GOval(getWidth() / 2 - WALL_WIDTH/8 + HANDLE_OFFSET, getHeight() - WALL_HEIGHT/3 + 2*HANDLE_OFFSET,HANDLE_SIZE, HANDLE_SIZE); +``` + +სახურავის ფორმად სამკუთხედი ავარჩიე. GObjectს ასეთი შვილობილი კლასი არ ჰყავს, ამიტომ GLine-ის საშუალებით +დავხატოთ. GLineს საწყისი და საბოლოო კოორდინატები გადაეცემა, რომელიც სახლის კედლების კოორდინატებზე დამოკიდებული +იქნება. კერძოთ, პირველი ხაზის საწყისი კოორდინატი იქნება ზემოთ აღწერილი GRect-ის საწყისი კოორდინატი, ხოლო +საბოლოო კოორდინატი სიმეტრიულობის გამო კანვასის შუაში იქნება. სახურავის სიმაღლე მუდმივ ცვლადად(ROOF_HEIGHT) +აღვწეროთ. მეორე ხაზის საწყისი კოორდინატი კი პირველი ხაზის საბოლოო კოორდინატი იქნება. მისი საბოლოო Y +კოორდინატი GRect ობიექტისას დაემთხვევა, ხოლო X კი WALL_WIDTHით იქნება დაშორებული. + +მივიღებთ კოდს: +```java +GLine line1 = new GLine(getWidth()/2 - WALL_WIDTH/2, getHeight() - WALL_HEIGHT, getWidth() / 2, + getHeight() - WALL_HEIGHT - ROOF_HEIGHT); +GLine line2 = new GLine(getWidth() / 2, getHeight() - WALL_HEIGHT - ROOF_HEIGHT, + getWidth()/2 + WALL_WIDTH/2, getHeight() - WALL_HEIGHT); +``` + +> *არ დაგვავიწყდეს, რომ ობიექტები აღწერის შემდეგ add ფუნქციის საშუალებით უნდა დავამატოთ.* + +## შესაძლო ხარვეზები ამოხსნის იმპლემენტაციისას +ყველაზე მთავარი შეცდომა, რაც შეიძლება ამ ამოცანაში გამოგვეპაროს არის ფუნქციების შესრულების მიმდევრობა. მაგალითად, +თუ გადავწყვეტთ, რომ ჯერ დავხატოთ ფანჯრები და შემდეგ კედლები, რომელსაც გავაფერადებთ, ამ შემთხვევაში უნდა +გავითვალისწინოთ, რომ კედლები გადაფარავს ფანჯრებს და ვერ დავინახავთ. ამ პრობლემის გამოსასწორებლად 2 გზა არსებობს: +* შევცვალოთ ფუნქციების შესრულების მიმდევრობა: ჯერ კედლები დავხატოთ, შემდეგ ფანჯრები +* გამოვიყენოთ sendToFront() ან sendForward() ფუნქციები. პირველი დახმარებით ობიექტი პირველ დონეზე გადავა, ხოლო +მეორეს დახმარებით კი ერთი დონით წინ გადავა. diff --git a/problem-set/loseEverything.java b/problem-set/loseEverything.java new file mode 100644 index 0000000..bbdf938 --- /dev/null +++ b/problem-set/loseEverything.java @@ -0,0 +1,39 @@ +import acm.program.*; +import acm.util.RandomGenerator; + +public class loseEverything extends ConsoleProgram { + + private RandomGenerator rgen = RandomGenerator.getInstance(); + private static final int balance = 1000; + + int askUser(String message, int lowerBound, int upperBound) { + int num = readInt(message); + while (num < lowerBound || num > upperBound) { + num = readInt("Wrong input, try again: "); + } + return num; + } + + private void simulateGame(int deposit) { + while (deposit > 0) { + int bet = askUser("Place your bet: ", 0, deposit); + int guess = askUser("Choose number between 0 and 36: ", 0, 36); + int num = rgen.nextInt(0, 36); + if (guess == num) { + deposit *= 2; + println("You Win! current balance: " + deposit); + } else { + deposit -= bet; + println("You lose. current balance: " + deposit); + } + } + + println("Game Over, Good Game"); + } + + public void run() { + int deposit = balance; + simulateGame(deposit); + } + +} \ No newline at end of file diff --git a/problem-set/loseEverythingExp.md b/problem-set/loseEverythingExp.md new file mode 100644 index 0000000..f02fdea --- /dev/null +++ b/problem-set/loseEverythingExp.md @@ -0,0 +1,75 @@ +# loseEverything + +## პრობლემა: +მოხმარებელს აქვს საწყისი თანხა 1000, თამაშობს მანამ სანამ არ წააგებს ყველაფერს. ყოველ +ჯერზე შეყავს ფსონის თანხა და რიცხვი რომელზეც დებს. ხდება რულეტკის დატრიალების +სიმულაცია(0-დან 36-მდე შემთხვევითი რიცხვი), მოგების შემთხვევაში მოთამაშეს +უორმაგედბა დადებული თანხა წაგების შემთხვევაში კი მოთამაშე კარგავს დადებულ თანხას. +მოთამაშე მოგებულია მაშინ თუ რულეტკაზე ამოსული რიცხვი ემთხვევა მის მიერ დადებულ +რიცხვს. პროგრამა ყოველ მოქმედებაზე უნდა ბეჭდავდეს შესაბამის შეტყობინებას, ადვილად +რომ გაერკვეს მოთამაშე, რა რიცხვი ამოვიდა, რამდენი წააგო, რამდენი მოიგო, რამდენი აქვს +ბალანსი. + +## ამოხსნა: +პირველ რიგში ჩვენი პრობლემა დავყოთ რამდენიმე ნაწილად: +* საწყისი ბალანსის ინიციალიზაცია +* თამაშის სიმულაცია (0 დან 36-დე რიცხვის არჩევა) +* მომხმარებლისთვის შეტყობინების გამოტანა + +--- + +### მომზადება პრობლემის გადასაჭრელად +კარგი იქნება, თუ საწყის თანხას მუდმივად(private static final int) გავიტანთ, რათა სურვილის შემთხვევაში მარტივად შევძლოთ შეცვლა. მოვიფიქროთ, თუ როგორ მოვახდენთ რულეტკის სიმულაციას. ამოხსნის გზის მოფიქრების შემდეგ ავაგოთ შესაბამისი დეკომპოზიციური სურათი. + +--- + +### ამოხსნა + +* ჯერ ვიფიქროთ რულეტკის დატრიალების სიმულაციაზე. + ვთვლით, რომ რულეტკა 0-დან 36-მდე თითოეულ რიცხვზე თანაბარი ალბათობით მოხვდება. პროგრამაში ეს შეგვიძლია randomGenerator-ის დახმარებით გავაკეთოთ. მის `readInt()` ფუნქციას თუ გადავცემთ 2 არგუმენტს, 0-სა და 36-ს, თანაბარი ალბათობით დაგვიბრუნებს ამ ინტერვალში რომელიმე რიცხვს. ერთი დატრიალების ფსევდო კოდი ასე გამოიყურება: + +--- + დავაგენერიროთ რიცხვი `num` 0-დან 36მდე; + თუ მომხმარებლის შემოყვანილი რიცხვი დაემთხვა `num`-ს + გავზარდოთ მომხმარებლის ბალანსი + წინააღმდეგ შემთხვევაში + შევამციროთ მის მიერ დადებული თანხის მიხედვით. +--- + +* მომხმარებლის მიერ ფსონი დადება +პირველ რიგში, მომხმარებელი დებს თანხას, შემდეგ კი ცდილობს გამოიცნოს რულეტკაზე რა რიცხვი მოვა. ორივეს იმპლემენტაცია შესაძლებელია `readInt()` ფუნქციით. უნდა გავითვალისწინოთ, რომ მომხმარებელი არასწორი რიცხვი არ შემოიყვანოს. მაგალითად, დარჩენილ ბალანსზე მეტი თანხა არ დადოს, ან არასწორი რიცხვის გამოცნობა არ სცადოს. ანუ, უნდა შევამოწმოთ მომხმარებლის მიერ შემოყვანილი რიცხვი, თუ რაიმე საზღვრებს არ სცდება. შესაბამისად, კარგი იქნება, თუ დავწერთ მეთოდს, რომელიც შემოაყვანინებს მომხმარებელს რიცხვს, გამოიტანს შესაბამის მესიჯს და შეამოწმებს ამ რიცხვს საზღვრებზე. მომხმარებელს იქამდე უნდა ვთხოვოთ რიცხვის შემოყვანა, სანამ სწორად არ მოახერხებს ამას. ამისთვის `while` ციკლი გამოგვადგება. მივიღებთ კოდს: + +```java + int askUser(String message, int lowerBound, int upperBound) { + int num = readInt(message); + while (num < lowerBound || num > upperBound) { + num = readInt("Wrong input, try again: "); + } + return num; + } +``` + +არ დაგვავიწყდეს, რომ მომხმარებელი მარტივად უნდა გაერკვეს რამდენი თანხა დარჩა, რამდენი წააგო და ა.შ. შესაბამისად, ყოველი მოგებული ან წაგებული თამაშის შემდეგ უნდა გამოვიტანოთ მისი ბალანსი. ეს ყველაფერი იქამდე უნდა გავაგრძელოთ, სანამ თანხა 0-ს არ გაუტოლდება. მივიღებთ კოდს: + +```java + while (deposit > 0) { + int bet = askUser("Place your bet: ", 0, deposit); + int guess = askUser("Choose number between 0 and 36: ", 0, 36); + int num = rgen.nextInt(0, 36); + if (guess == num) { + deposit *= 2; + println("You Win! current balance: " + deposit); + } else { + deposit -= bet; + println("You lose. current balance: " + deposit); + } + } + + println("Game Over, Good Game"); +``` + +## შესაძლო ხარვეზები ამოხსნის იმპლემენტაციისას +მთავარი შეცდომა, რაც შეიძლება დავუშვათ, არის მომხმარებლის "ნდობა" და მისი შემოყვანილი რიცხვის არ-შემოწმება. არასწორმა რიცხვმა შეიძლება სრულიად არიოს პროგრამა. ამიტომ არის გამოსადეგი `askUser` მეთოდი. + +## ამოხსნის ალტერნატიული გზა +შეგვეძლო 1 მთლიანი დატრიალების სიმულაციის ნაცვლად შეგვექმნა მეთოდი, რომელიც მთელ რიცხვს დააბრუნებდა მოთამაშის არჩეული რიცხვის მიხედვით. მაგალითად, თუ მოიგებდა დავაბრუნებდით რა თანხა მოიგო, თუ წააგებდა - რა წააგო. სანამ ბალანსი 0-ზე მეტი იქნებოდა, გამოვაკლებდით ამ ფუნქციის მიერ დაბრუნებულ რიცხვს. diff --git a/problem-set/tossesFor3Heads.java b/problem-set/tossesFor3Heads.java new file mode 100644 index 0000000..535a23f --- /dev/null +++ b/problem-set/tossesFor3Heads.java @@ -0,0 +1,38 @@ +import acm.program.*; +import acm.util.RandomGenerator; + +public class tossesFor3Heads extends ConsoleProgram { + + private RandomGenerator rand = RandomGenerator.getInstance(); + private static final int numExperiments = 100000; + private static final int numHeads = 3; + + public void run() { + println(avrgTosses(numHeads, numExperiments)); + } + + private double avrgTosses(int numHeads, int numExperiments) { + int average = 0; + for (int i = 0; i < numExperiments; i++) { + average += simulation(numHeads); + } + + return (double) average / numExperiments; + } + + private int simulation(int numHeads) { + int heads = 0; + int flips = 0; + while (heads != numHeads) { + boolean flip = rand.nextBoolean(); + if (flip) { + heads++; + } else { + heads = 0; + } + flips++; + } + + return flips; + } +} \ No newline at end of file diff --git a/problem-set/tossesFor3HeadsExp.md b/problem-set/tossesFor3HeadsExp.md new file mode 100644 index 0000000..cb6c54b --- /dev/null +++ b/problem-set/tossesFor3HeadsExp.md @@ -0,0 +1,65 @@ +# tossesFor3heads + +## პრობლემა: +თქვენი ამოცანაა გააკეთოთ მონეტის აგდების სიმულაციები და დათვალოთ საშუალოდ +რამდენჯერ უნდა ავაგდოთ მონეტა რათა 3-ჯერ ზედიზედ ამოვიდეს ბორჯღალო. + +## ამოხსნა: +პირველ რიგში ჩვენი პრობლემა დავყოთ რამდენიმე ნაწილად: +* დავითვალოთ ერთი ექსპერიმენტის დროს, რამდენ აგდებაში მოვა ბორჯღალო. +* ასეთი ექსპერიმენტი ჩავატაროთ მუდმივა `numExperiments` რაოდენობაჯერ. +* ავჯამოთ მიღებული შედეგები და გავყოთ ექსპერიმენტების რაოდენობაზე. + +--- + +### მომზადება პრობლემის გადასაჭრელად +უმჯობესი იქნება თუ ექსპერიმენტების რაოდენობასა და ზედიზედ მოსული ბორჯღალოების რაოდენობას `private static final` +ცვლადებად გავიტანთ. შედეგად, მარტივად შეგვეძლება კოდში სურვილის მიხედვით ცვლილებების შეტანა. + +--- + +### დეკომპოზიციური სურათის აგება +`for` ციკლით, შეგვიძლია ექსპერიმენტების რაოდენობაჯერ ავჯამოთ `simulation` ფუნქციის მიერ დაბრუნებული მნიშვნელობა, რომელიც, როგორც ზემოთ აღვწერე, ერთ ექსპერიმენტს ჩაატარებს და დააბრუნებს, რამდენ აგდებაში მოვიდა `numheads` ბორჯღალო ზედიზედ. ამის შემდეგ გავყოთ მიღებული ჯამი ექსპერიმენტების რაოდენობაზე. მივიღებთ კოდს: + +```java + private double avrgTosses(int numHeads, int numExperiments) { + int average = 0; + for (int i = 0; i < numExperiments; i++) { + average += simulation(numHeads); + } + + return (double) average / numExperiments; + } +``` + +--- + +### `simulation` ფუნქციის იმპლემენტაცია +პირველ რიგში დავფიქრდეთ, რა ცვლადები დაგვჭირდება. აუცილებლად დაგვჭირდება შევინახოთ მონეტის აგდებების რაოდენობა. დავარქვათ მას `flips`. როგორმე უნდა დავიმახსოვროთ, თუ რამდენჯერ მოვიდა ზედიზედ ბორჯღალო. ამისთვის მეორე ცვლადი დაგვჭირდება, რომელსაც `heads` დავარქმევ. ჩვენ მანამდე უნდა "ავაგდოთ" მონეტა სანამ `heads` ცვლადი 3-ის ტოლი არ გახდება. ამისთვის გამოვიყენოთ `while` ციკლი. მონეტის აგდების სიმულაციის ერთ-ერთი და ალბათ ყველაზე მარტივი გზაა `RandomGenerator`-ის ფუნქციის `nextBoolean()`-ის საშუალებით შემთხვევითად დავაგენერიროთ `boolean`. დაგვიბრუნებს true-ს ან false-ს თანაბარი ალბათობით. პირობითად შეგვიძლია true ავიღოთ ბორჯღალოს მოსვლად, ხოლო false - არიოლად. +რამდენჯერად ბორჯღალო მოვა გავზარდოთ `heads` ცვლადი. არ დაგვავიწყდეს, რომ თუ არიოლი მოვიდა `heads` უნდა გავანულოთ, ანუ ექსპერიმენტი ფაქტობრივად თავიდან იწყება. მივიღებთ კოდს: + +```java +private int simulation(int numHeads) { + int heads = 0; + int flips = 0; + while (heads != numHeads) { + boolean flip = rand.nextBoolean(); + if (flip) { + heads++; + } else { + heads = 0; + } + flips++; + } + + return flips; + } +``` + +## შესაძლო ხარვეზები ამოხსნის იმპლემენტაციისას +> მთავარი ხარვეზი, რაც შეიძლება დავუშვათ ამოხსნისას, არის არიოლის მოსვლისას ექსპერიმენტის ჩვეულებრივად გაგრძელება `heads` ცვლადის განულების გარეშე. მივიღებთ არასწორ შედეგს. + + +## ალტერნატიული ამოხსნა +> ამოხსნაში შეგვიძლია მონეტის აგდების სიმულაცია მოვახდინოთ სხვანაირად. `boolean`-ის დაგენერირების ნაცვლად შეგვიძლია +სიმეტრიულ ინტერვალში დავაგენერიროთ რიცხვი და თუ პირველ ნახევარში ჩავარდა პირობითად ბორჯღალოდ ავიღოთ, თუ მეორეში -არიოლად. ასევე `flips` ცვლადის ნაცვლად შეგვიძლია 3 `boolean` ცვლადი გვქონდეს, თუმცა ასეთ შემთხვევაში კოდის შეცვლის სურვილი თუ გვექნება(ვთქვათ, გადავწყვიტეთ დავითვალოდ საშუალოდ 4 ბორჯღალო რამდენ აგდებაში მოდის) უფრო გაგვიჭირდება.