Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c054bfc3a7 | ||
|
|
375cc0b5f3 | ||
|
|
2a293be0d8 | ||
|
|
79594de5a9 | ||
|
|
638730d95b | ||
|
|
14af49c9e8 |
@@ -47,7 +47,7 @@ The bot follows the five steps of the lecture. Every step is one function with t
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
In the run that made these screenshots the bot reached **16.6 cookies per second** after 300 seconds. Your number will differ: it depends on the speed of your machine and the network.
|
In the run that made these screenshots the bot reached **29 cookies per second** after 300 seconds (the first release reached 16.6). Your number will differ: it depends on the speed of your machine and the network.
|
||||||
|
|
||||||
### Parts of the repository
|
### Parts of the repository
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ document of a type. `Primary File` may contain a glob (e.g.
|
|||||||
| BC | Business Case | docs/business-case.md | 002 |
|
| BC | Business Case | docs/business-case.md | 002 |
|
||||||
| SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 |
|
| SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 |
|
||||||
| PP | Project Plan | docs/project-plan.md | 002 |
|
| PP | Project Plan | docs/project-plan.md | 002 |
|
||||||
| MIL | Milestone / Gateway | docs/milestones/mil-*.md | 006 |
|
| MIL | Milestone / Gateway | docs/milestones/mil-*.md | 007 |
|
||||||
| ADR | Architecture Decision Record | docs/adr/adr-*.md | 0002 |
|
| ADR | Architecture Decision Record | docs/adr/adr-*.md | 0002 |
|
||||||
| TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 |
|
| TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 |
|
||||||
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 014 |
|
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 018 |
|
||||||
|
|
||||||
## Languages
|
## Languages
|
||||||
|
|
||||||
|
|||||||
@@ -1,123 +1,253 @@
|
|||||||
INFO:__main__:Read 15 cookies
|
INFO:__main__:Read 15 cookies
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 18 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 18 cookies
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 1 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:2 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:6 of 300 seconds played
|
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 100 cookies
|
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 1 did not raise the owned amount
|
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:11 of 300 seconds played
|
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 115 cookies
|
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 1 did not raise the owned amount
|
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:16 of 300 seconds played
|
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 133 cookies
|
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 1 did not raise the owned amount
|
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:23 of 300 seconds played
|
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 20 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 20 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:3 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:5 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 23 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 23 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:6 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 27 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 27 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:8 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 31 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 31 cookies
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 0 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:9 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:28 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:11 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 35 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 35 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:12 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:14 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 40 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 40 cookies
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:33 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:15 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:17 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 46 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 46 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:18 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:20 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:21 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 53 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 53 cookies
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 0 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:23 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:39 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:25 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:26 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 61 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 61 cookies
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 0 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:28 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:45 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:30 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 153 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:31 of 300 seconds played
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 1 did not raise the owned amount
|
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:51 of 300 seconds played
|
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 70 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 70 cookies
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 0 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:33 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:56 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:34 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 175 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:36 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:62 of 300 seconds played
|
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 81 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 81 cookies
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:67 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:37 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:38 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:40 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:41 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:43 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.store:Bought upgrade 0
|
||||||
|
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 0 did not raise the owned amount
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:44 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:46 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 93 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 93 cookies
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 0 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:47 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:72 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:49 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:50 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 107 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 107 cookies
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 0 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:51 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:78 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:52 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 100 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:54 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:55 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:56 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 123 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 123 cookies
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 0 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:58 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:83 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:59 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 115 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:60 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:62 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:63 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 141 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 141 cookies
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 0 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:64 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:89 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:65 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:67 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 133 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:68 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:69 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:71 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 153 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:72 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:74 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:75 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:77 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 175 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:78 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:79 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:81 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 162 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 162 cookies
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 0 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:82 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:95 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:83 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:101 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:85 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:86 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 202 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 202 cookies
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 1 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:87 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:107 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:88 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 186 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:90 of 300 seconds played
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 0 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:91 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:112 of 300 seconds played
|
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:117 of 300 seconds played
|
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 232 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 232 cookies
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 1 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:92 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:123 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:93 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:95 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:96 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 186 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:97 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:99 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:100 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:101 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 214 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 214 cookies
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 0 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:103 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:129 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:104 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:134 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:105 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:106 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:108 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 267 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 267 cookies
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:140 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:109 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:146 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:111 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 306 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:112 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:151 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:113 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:157 of 300 seconds played
|
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 246 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 246 cookies
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 0 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:114 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:162 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:116 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:167 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:118 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 352 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:120 of 300 seconds played
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 1 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:122 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:172 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 306 cookies
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:178 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:123 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:124 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:126 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:127 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:128 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 283 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 283 cookies
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:184 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:129 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:190 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:131 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:132 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:133 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:135 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:136 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 352 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:137 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:139 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:140 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:141 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:143 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:144 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 325 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 325 cookies
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 1 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:146 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:195 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:148 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:201 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:150 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:152 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:153 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:155 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 405 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 405 cookies
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 1 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:157 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:207 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:158 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:213 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:160 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:161 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:163 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:165 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 374 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 374 cookies
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:218 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:167 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:223 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:168 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:229 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:170 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 466 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:172 of 300 seconds played
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 1 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:174 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:234 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:175 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:240 of 300 seconds played
|
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 430 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 430 cookies
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 0 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:177 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:246 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:179 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:251 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:180 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:256 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:182 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:183 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:184 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:186 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 466 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:187 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:188 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:190 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:191 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:192 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:193 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:195 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:196 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.store:Bought upgrade 1
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:197 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:199 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:201 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:202 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:204 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 536 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 536 cookies
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 1 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:205 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:262 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:207 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:268 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:208 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:273 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:209 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:210 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 494 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 494 cookies
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:278 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:212 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:283 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:213 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:289 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:214 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:216 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:217 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 616 cookies
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 616 cookies
|
||||||
WARNING:cookie_clicker_bot.application.use_cases.run_bot:Purchase of product 1 did not raise the owned amount
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:219 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:220 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:222 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:223 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:224 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 568 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:226 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:229 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:231 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:233 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:234 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 654 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:236 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:238 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:239 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:241 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:242 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:243 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 751 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:245 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:247 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:248 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:250 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:251 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:253 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 708 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:254 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:257 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:259 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:261 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:263 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:265 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 814 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:267 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:269 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:271 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:273 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:275 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:277 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:279 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 1 for 936 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:281 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:282 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:284 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:285 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:286 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:288 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:289 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:291 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Bought product 0 for 864 cookies
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:292 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:294 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:294 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:299 of 300 seconds played
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:295 of 300 seconds played
|
||||||
INFO:cookie_clicker_bot.application.use_cases.run_bot:Run finished at 16.6 cookies per second
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:297 of 300 seconds played
|
||||||
Result : 16.6
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:298 of 300 seconds played
|
||||||
exit 0
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:300 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:302 of 300 seconds played
|
||||||
|
INFO:cookie_clicker_bot.application.use_cases.run_bot:Run finished at 29 cookies per second
|
||||||
|
Result : 29
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 663 KiB After Width: | Height: | Size: 665 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
@@ -0,0 +1,74 @@
|
|||||||
|
# Purchase strategy improvement
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
| Key | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| ID | MIL-006 |
|
||||||
|
| CrossReference | [BC-001] |
|
||||||
|
| Language | en |
|
||||||
|
| Domain | it |
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
| Date | Status | Author | Reviewer | Change | Commit |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| 2026-10-28 | Deprecated | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version (reviewed in RC-014) | [638730d] |
|
||||||
|
| 2026-10-28 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Task 1 changed after the first measurement run: one product per store visit instead of waiting for a price change (reviewed in RC-016) | [375cc0b] |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Decide whether the bot reaches a higher cookies-per-second (CPS) rate than the 16.6 of the first release by buying more reliably and more often. In the Docker run of MIL-004 about 40 percent of the purchase attempts failed: each failure came right after a successful purchase in the same store visit, because the game had not yet redrawn the store flags.
|
||||||
|
|
||||||
|
## Deliverable
|
||||||
|
|
||||||
|
The functions `buy_upgrade`, `get_store_items` and `run_bot` changed so that the bot buys at most one product per store visit and therefore always reads the store after the game has redrawn it, visits the store every second and also buys affordable upgrades; a Docker run of 300 seconds with its CPS, log and refreshed screenshot of step 5; the README and the wiki pages in English, Danish and Bahasa Malaysia updated with the new result and what it taught.
|
||||||
|
|
||||||
|
## Go / No-Go Criteria
|
||||||
|
|
||||||
|
| # | Criterion (objectively checkable) | Go | No-Go |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | In a 300 second Docker run, fewer than 5 percent of the purchase attempts log "did not raise the owned amount" | Met | Not met |
|
||||||
|
| 2 | The CPS of that run is higher than the 16.6 of the first release | Met | Not met |
|
||||||
|
| 3 | `python -m pytest`, `ruff check` and `mypy --strict` pass; `selenium` is still the only runtime dependency | Met | Not met |
|
||||||
|
| 4 | The source review record says Go | Met | Not met |
|
||||||
|
| 5 | The README and all three wiki languages show the same new CPS and screenshot | Met | Not met |
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
| Depends on | Reason |
|
||||||
|
| --- | --- |
|
||||||
|
| MIL-005 | Needs the finished bot, the Docker run and the wiki it updates |
|
||||||
|
|
||||||
|
## Traceability
|
||||||
|
|
||||||
|
| Business Case objective / KPI / user story | Reference |
|
||||||
|
| --- | --- |
|
||||||
|
| O1, O3, O4 in [BC-001] | Success criterion 2 (CPS reported) and the learning record of the wiki |
|
||||||
|
|
||||||
|
## Ownership
|
||||||
|
|
||||||
|
| Role | Stakeholder ID (SA) |
|
||||||
|
| --- | --- |
|
||||||
|
| Owner | S01 |
|
||||||
|
| Approving reviewer | S01 |
|
||||||
|
|
||||||
|
## Target Date
|
||||||
|
|
||||||
|
2026-11-03 - within the one-week limit of [BC-001]
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
| # | Task | Summary | Needs its own Use Case/User Story? | Reference |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| 1 | Wait for the store redraw after a purchase | After a successful purchase the store flags and prices are stale until the game redraws, so the next choice clicks a product that is no longer affordable. The game updates the price and owned text at once but the affordable flag only at its next redraw, so the price text is not a redraw signal (found in the first measurement run). Buy at most one product per store visit, so the next read comes a second later, after the redraw; the log of MIL-004 shows the failures follow a purchase. | No | |
|
||||||
|
| 2 | Visit the store every second | The store is visited every 5 seconds and the loop stops buying at the first failure, so cookies wait idle. Shorten `STORE_CHECK_INTERVAL_SECONDS` and keep buying while something is affordable; measure that clicks per second do not drop. | No | |
|
||||||
|
| 3 | Buy affordable upgrades | Upgrades such as the cursor and grandma boosts raise the CPS cheaply. Read the enabled upgrade crates in the `upgrades` area and click the cheapest first, behind the existing port with a fake in the tests. | No | |
|
||||||
|
| 4 | Measure the new strategy in Docker | Run the 300 second headless Docker run again, compare the CPS and the failure rate with the first release (16.6, about 40 percent) and refresh the screenshot of step 5. Environment: Docker container. | No | |
|
||||||
|
| 5 | Update the README and the wiki | Put the new CPS, the screenshot and what the improvement taught into the README and the English, Danish and Bahasa Malaysia wiki pages of step 4 and step 5. | No | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[BC-001]: ../business-case.md
|
||||||
|
[638730d]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/048-the-coockie-factory/commit/638730d95ba24ba77c6448730d7999aa3e90d863
|
||||||
|
[375cc0b]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/048-the-coockie-factory/commit/375cc0b5f3919b00daeb2f8f5fcc81de25acfc4c
|
||||||
@@ -4,20 +4,21 @@
|
|||||||
| Key | Value |
|
| Key | Value |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| ID | PP-001 |
|
| ID | PP-001 |
|
||||||
| CrossReference | [BC-001], [SA-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005] |
|
| CrossReference | [BC-001], [SA-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006] |
|
||||||
| Language | en |
|
| Language | en |
|
||||||
| Domain | it |
|
| Domain | it |
|
||||||
|
|
||||||
## Version History
|
## Version History
|
||||||
| Date | Status | Author | Reviewer | Change | Commit |
|
| Date | Status | Author | Reviewer | Change | Commit |
|
||||||
| --- | --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- | --- |
|
||||||
| 2026-10-28 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version (reviewed in RC-003) | [96a9a78] |
|
| 2026-10-28 | Deprecated | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version (reviewed in RC-003) | [96a9a78] |
|
||||||
|
| 2026-10-28 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Added MIL-006 purchase strategy improvement after the user asked for it in chat (reviewed in RC-015) | [638730d] |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
This plan schedules the five phases that deliver the Cookie Clicker bot inside the one-week limit of [BC-001].
|
This plan schedules the six phases that deliver the Cookie Clicker bot inside the one-week limit of [BC-001].
|
||||||
|
|
||||||
## Planning Assumptions
|
## Planning Assumptions
|
||||||
|
|
||||||
@@ -35,6 +36,7 @@ This plan schedules the five phases that deliver the Cookie Clicker bot inside t
|
|||||||
| Upgrades and run | [MIL-003] | 2026-10-30 to 2026-10-31 | 2026-10-31 | S01 | none (technical tasks) | strategy, run_bot, main | |
|
| Upgrades and run | [MIL-003] | 2026-10-30 to 2026-10-31 | 2026-10-31 | S01 | none (technical tasks) | strategy, run_bot, main | |
|
||||||
| Screenshots and README | [MIL-004] | 2026-10-31 to 2026-11-02 | 2026-11-02 | S01 | none (technical tasks) | Docker run, screenshots, README | |
|
| Screenshots and README | [MIL-004] | 2026-10-31 to 2026-11-02 | 2026-11-02 | S01 | none (technical tasks) | Docker run, screenshots, README | |
|
||||||
| Wiki and release | [MIL-005] | 2026-11-02 to 2026-11-04 | 2026-11-04 | S01 | none (technical tasks) | Wiki en, da, ms and final checks | |
|
| Wiki and release | [MIL-005] | 2026-11-02 to 2026-11-04 | 2026-11-04 | S01 | none (technical tasks) | Wiki en, da, ms and final checks | |
|
||||||
|
| Purchase strategy improvement | [MIL-006] | 2026-10-28 to 2026-11-03 | 2026-11-03 | S01 | none (technical tasks) | Reliable store visits, upgrades bought, a higher CPS in Docker, README and wiki updated | |
|
||||||
|
|
||||||
```plantuml
|
```plantuml
|
||||||
@startgantt
|
@startgantt
|
||||||
@@ -48,7 +50,9 @@ Project starts 2026-10-28
|
|||||||
[MIL-002 Go/No-Go] happens 2026-10-30
|
[MIL-002 Go/No-Go] happens 2026-10-30
|
||||||
[MIL-003 Go/No-Go] happens 2026-10-31
|
[MIL-003 Go/No-Go] happens 2026-10-31
|
||||||
[MIL-004 Go/No-Go] happens 2026-11-02
|
[MIL-004 Go/No-Go] happens 2026-11-02
|
||||||
|
[Purchase strategy] starts 2026-10-28 and ends 2026-11-03
|
||||||
[MIL-005 Go/No-Go] happens 2026-11-04
|
[MIL-005 Go/No-Go] happens 2026-11-04
|
||||||
|
[MIL-006 Go/No-Go] happens 2026-11-03
|
||||||
@endgantt
|
@endgantt
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -63,13 +67,16 @@ Project starts 2026-10-28
|
|||||||
| README | MIL-004 |
|
| README | MIL-004 |
|
||||||
| Wiki in en, da and ms | MIL-005 |
|
| Wiki in en, da and ms | MIL-005 |
|
||||||
| Description and topics on the git host | MIL-001 |
|
| Description and topics on the git host | MIL-001 |
|
||||||
|
| Higher CPS by a more reliable purchase strategy (objective O1) | MIL-006 |
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
```
|
```
|
||||||
MIL-001 → MIL-002 → MIL-003 → MIL-004 → MIL-005
|
MIL-001 → MIL-002 → MIL-003 → MIL-004 → MIL-005 → MIL-006
|
||||||
```
|
```
|
||||||
|
|
||||||
|
MIL-006 was added after the first release; it runs beside the end of the window and must finish by 2026-11-03, one day before the end date.
|
||||||
|
|
||||||
A No-Go on a phase returns it to its owner for rework and moves every later gateway by the rework time; the end date 2026-11-04 moves only if S01 decides so and records the reason in Planning Assumptions. S01 is owner, reviewer and the only escalation point.
|
A No-Go on a phase returns it to its owner for rework and moves every later gateway by the rework time; the end date 2026-11-04 moves only if S01 decides so and records the reason in Planning Assumptions. S01 is owner, reviewer and the only escalation point.
|
||||||
|
|
||||||
## Plan Risks
|
## Plan Risks
|
||||||
@@ -81,6 +88,7 @@ A No-Go on a phase returns it to its owner for rework and moves every later gate
|
|||||||
| Repository name differs from the one in the request | Metadata set on the wrong repository | Use the repository `origin` points to; reported in the plan summary |
|
| Repository name differs from the one in the request | Metadata set on the wrong repository | Use the repository `origin` points to; reported in the plan summary |
|
||||||
| Token handling (Business Case risk: a token leaks) | Secret exposure | Tokens only read from `.env` by scripts outside the project, never printed |
|
| Token handling (Business Case risk: a token leaks) | Secret exposure | Tokens only read from `.env` by scripts outside the project, never printed |
|
||||||
| Live game changes (Business Case risk: page structure changes) | Bot breaks | Ids in `constants.py`; run the capture early in MIL-004 |
|
| Live game changes (Business Case risk: page structure changes) | Bot breaks | Ids in `constants.py`; run the capture early in MIL-004 |
|
||||||
|
| The new strategy does not raise the CPS | MIL-006 returns No-Go | Measure after each change; keep the first release as the fallback |
|
||||||
|
|
||||||
## Open Issues
|
## Open Issues
|
||||||
|
|
||||||
@@ -112,4 +120,6 @@ A No-Go on a phase returns it to its owner for rework and moves every later gate
|
|||||||
[MIL-003]: ./milestones/mil-003-upgrades-and-five-minute-run.md
|
[MIL-003]: ./milestones/mil-003-upgrades-and-five-minute-run.md
|
||||||
[MIL-004]: ./milestones/mil-004-screenshots-and-readme.md
|
[MIL-004]: ./milestones/mil-004-screenshots-and-readme.md
|
||||||
[MIL-005]: ./milestones/mil-005-wiki-and-release.md
|
[MIL-005]: ./milestones/mil-005-wiki-and-release.md
|
||||||
|
[MIL-006]: ./milestones/mil-006-purchase-strategy.md
|
||||||
[96a9a78]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/048-the-coockie-factory/commit/96a9a78f010ce8ae3fce8d84602317d8b5808a13
|
[96a9a78]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/048-the-coockie-factory/commit/96a9a78f010ce8ae3fce8d84602317d8b5808a13
|
||||||
|
[638730d]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/048-the-coockie-factory/commit/638730d95ba24ba77c6448730d7999aa3e90d863
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# SQA Review Record
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
| Key | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| ID | RC-014 |
|
||||||
|
| CrossReference | [MIL-006], [QC-MIL-001] |
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
| Date | Status | Author | Reviewer | Change | Commit |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| 2026-10-28 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | [638730d] |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Artifact Under Review
|
||||||
|
|
||||||
|
- Instance reviewed: [MIL-006]
|
||||||
|
- Checklist used: [QC-MIL-001]
|
||||||
|
- Scope: full review
|
||||||
|
- Language and domain: en / it (the artifact's Metadata rows)
|
||||||
|
- Language reviewer: S01 (confirmed criteria 3, 5 and 9 of QC-LANG-001 in chat on 2026-10-28)
|
||||||
|
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`)
|
||||||
|
|
||||||
|
## Checklist Results
|
||||||
|
|
||||||
|
| # | Criterion | Status | Evidence/Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | A concrete deliverable is defined for every gate | Pass | Deliverable names the changed functions, a Docker run with CPS and log, and the updated README and wiki. |
|
||||||
|
| 2 | Explicit Go/No-Go criteria are stated for each gate | Pass | Five checkable Go criteria (failure rate below 5 percent, CPS above 16.6, checks green, review Go, same result in all docs). |
|
||||||
|
| 3 | Dependencies on other milestones are explicitly mapped | Pass | Depends on MIL-005. |
|
||||||
|
| 4 | Each milestone is traceable to a Business Case objective or KPI | Pass | Traces to O1, O3 and O4 of the Business Case. |
|
||||||
|
| 5 | Milestone owner and approving reviewer are identified | Pass | S01 owner and approving reviewer. |
|
||||||
|
| 6 | Milestone has a defined target date consistent with project constraints | Pass | Target 2026-11-03 is inside the window and equals the Project Plan decision date. |
|
||||||
|
|
||||||
|
## Language and Domain Results
|
||||||
|
|
||||||
|
| # | Criterion | Status | Evidence/Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | The Metadata table has a `Language` row and a `Domain` row, and neither is a placeholder | Pass | Metadata has Language en and Domain it. |
|
||||||
|
| 2 | `Language` is a BCP 47 code and `Domain` is a value from the registry's domain list | Pass | en is a BCP 47 code and it is in the registry domain list. |
|
||||||
|
| 3 | The content (prose and table cells) is written in the stated language | Pass | Confirmed by S01 in chat on 2026-10-28 (named human reviewer). Content is English. |
|
||||||
|
| 4 | The register matches the one the registry gives for the artifact type | Pass | Register: IT Executive English. |
|
||||||
|
| 5 | Domain terms are the PO terms of the domain's dictionary, with no synonyms | Pass | Confirmed by S01 in chat on 2026-10-28 (named human reviewer). Ordinary IT terms; no dictionary exists or is needed. |
|
||||||
|
| 6 | Metadata keys, section headings, IDs and statuses are in English | Pass | Headings, keys, IDs and statuses are English. |
|
||||||
|
| 7 | No translated twin (`<name>.<language>.md`) exists beside the document | Pass | No translated twin exists. |
|
||||||
|
| 8 | A change of language or domain since the previous accepted version has a Version History row and was reviewed again | N-A | No change of language or domain. |
|
||||||
|
| 9 | A reviewer competent in the domain, and in the language, has confirmed that the domain terms are used correctly | Pass | Confirmed by S01 in chat on 2026-10-28 (named human reviewer). S01 is competent in English and in IT. |
|
||||||
|
| 10 | Abbreviations are spelled out on first use, in the stated language | Pass | Abbreviations are spelled out at first use. |
|
||||||
|
|
||||||
|
## Overall Verdict
|
||||||
|
|
||||||
|
Go - All Mandatory criteria pass; the language criteria were confirmed by S01. Stand-in review, not independent.
|
||||||
|
|
||||||
|
## Action Items
|
||||||
|
|
||||||
|
| Action | Owner | Due |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| None | S01 | - |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[MIL-006]: ../../milestones/mil-006-purchase-strategy.md
|
||||||
|
[QC-MIL-001]: ../../../framework/qc/artifacts/qc-milestones-gateways.md
|
||||||
|
[QC-LANG-001]: ../../../framework/qc/artifacts/qc-language-domain.md
|
||||||
|
[638730d]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/048-the-coockie-factory/commit/638730d95ba24ba77c6448730d7999aa3e90d863
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
# SQA Review Record
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
| Key | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| ID | RC-015 |
|
||||||
|
| CrossReference | [PP-001], [QC-PP-001] |
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
| Date | Status | Author | Reviewer | Change | Commit |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| 2026-10-28 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | [638730d] |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Artifact Under Review
|
||||||
|
|
||||||
|
- Instance reviewed: PP-001 version 2 (adds MIL-006) ([PP-001])
|
||||||
|
- Checklist used: [QC-PP-001]
|
||||||
|
- Scope: full review
|
||||||
|
- Language and domain: en / it (the artifact's Metadata rows)
|
||||||
|
- Language reviewer: S01 (confirmed criteria 3, 5 and 9 of QC-LANG-001 in chat on 2026-10-28)
|
||||||
|
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`)
|
||||||
|
|
||||||
|
## Checklist Results
|
||||||
|
|
||||||
|
| # | Criterion | Status | Evidence/Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | The plan states its start date, end date and the constraint it must fit | Pass | Unchanged from RC-003 and rechecked with MIL-006 included. |
|
||||||
|
| 2 | Every milestone or gateway is listed once with its document, decision date and owner | Pass | Unchanged from RC-003 and rechecked with MIL-006 included. |
|
||||||
|
| 3 | Every decision date lies inside the plan's window and is consistent with the dates in the linked `MIL-*` document | Pass | Decision dates equal the Target Date of MIL-001 to MIL-006 and lie inside the window. |
|
||||||
|
| 4 | Every in-scope item of the Business Case is delivered at a named gateway (scope coverage) | Pass | Scope Coverage has a row for the purchase strategy improvement, mapped to MIL-006 and objective O1. |
|
||||||
|
| 5 | Dependencies between gateways are stated, acyclic and consistent with the schedule order | Pass | Chain MIL-001 to MIL-006 is acyclic; MIL-006 follows MIL-005. |
|
||||||
|
| 6 | The plan states what happens to later gateways and to the end date when a gateway is No-Go | Pass | Unchanged from RC-003 and rechecked with MIL-006 included. |
|
||||||
|
| 7 | Owners, reviewers and escalation roles use Stakeholder Analysis IDs and match the governance RACI | N-A | No governance document is in scope of the Business Case; the only owner ID is S01, as in the MIL Ownership tables. |
|
||||||
|
| 8 | Planning assumptions are listed with their source, and a changed baseline is recorded with its reason | Pass | Unchanged from RC-003 and rechecked with MIL-006 included. |
|
||||||
|
| 9 | Plan risks are listed with impact and a mitigation, and trace to Business Case risks where they come from there | Pass | Plan Risks list a new risk (CPS does not rise) with a mitigation; two risks trace to Business Case risks. |
|
||||||
|
| 10 | Open issues are listed, and none contradicts a statement elsewhere in the plan | Pass | Open Issues do not contradict the new row. |
|
||||||
|
| 11 | Each gateway's main deliverable is stated and matches the deliverable of its `MIL-*` document | Pass | The main deliverable of MIL-006 in the plan matches its Deliverable section. |
|
||||||
|
| 12 | Gateways that deliver user stories name them, and those that do not say why | Pass | Unchanged from RC-003 and rechecked with MIL-006 included. |
|
||||||
|
| 13 | The schedule view (table, Gantt) and the dependency view agree with each other | Pass | Gantt bars, table and the chain agree. |
|
||||||
|
| 14 | Each gateway is linked to its issue-tracker milestone, or marked as not yet synced | Pass | Milestone column blank until the sync. |
|
||||||
|
| 15 | The plan is a singleton: one Project Plan per project, changes recorded in Version History | Pass | Unchanged from RC-003 and rechecked with MIL-006 included. |
|
||||||
|
|
||||||
|
## Language and Domain Results
|
||||||
|
|
||||||
|
| # | Criterion | Status | Evidence/Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | The Metadata table has a `Language` row and a `Domain` row, and neither is a placeholder | Pass | Metadata has Language en and Domain it. |
|
||||||
|
| 2 | `Language` is a BCP 47 code and `Domain` is a value from the registry's domain list | Pass | en is a BCP 47 code and it is in the registry domain list. |
|
||||||
|
| 3 | The content (prose and table cells) is written in the stated language | Pass | Confirmed by S01 in chat on 2026-10-28 (named human reviewer). Content is English. |
|
||||||
|
| 4 | The register matches the one the registry gives for the artifact type | Pass | Register: IT Executive English. |
|
||||||
|
| 5 | Domain terms are the PO terms of the domain's dictionary, with no synonyms | Pass | Confirmed by S01 in chat on 2026-10-28 (named human reviewer). Ordinary IT terms; no dictionary exists or is needed. |
|
||||||
|
| 6 | Metadata keys, section headings, IDs and statuses are in English | Pass | Headings, keys, IDs and statuses are English. |
|
||||||
|
| 7 | No translated twin (`<name>.<language>.md`) exists beside the document | Pass | No translated twin exists. |
|
||||||
|
| 8 | A change of language or domain since the previous accepted version has a Version History row and was reviewed again | N-A | No change of language or domain. |
|
||||||
|
| 9 | A reviewer competent in the domain, and in the language, has confirmed that the domain terms are used correctly | Pass | Confirmed by S01 in chat on 2026-10-28 (named human reviewer). S01 is competent in English and in IT. |
|
||||||
|
| 10 | Abbreviations are spelled out on first use, in the stated language | Pass | Abbreviations are spelled out at first use. |
|
||||||
|
|
||||||
|
## Overall Verdict
|
||||||
|
|
||||||
|
Go - Full re-review of version 2; all Mandatory criteria pass or are not applicable; the language criteria were confirmed by S01. Stand-in review, not independent.
|
||||||
|
|
||||||
|
## Action Items
|
||||||
|
|
||||||
|
| Action | Owner | Due |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| None | S01 | - |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[PP-001]: ../../project-plan.md
|
||||||
|
[QC-PP-001]: ../../../framework/qc/artifacts/qc-project-plan.md
|
||||||
|
[QC-LANG-001]: ../../../framework/qc/artifacts/qc-language-domain.md
|
||||||
|
[638730d]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/048-the-coockie-factory/commit/638730d95ba24ba77c6448730d7999aa3e90d863
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# SQA Review Record
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
| Key | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| ID | RC-016 |
|
||||||
|
| CrossReference | [MIL-006], [QC-MIL-001] |
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
| Date | Status | Author | Reviewer | Change | Commit |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| 2026-10-28 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | [375cc0b] |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Artifact Under Review
|
||||||
|
|
||||||
|
- Instance reviewed: [MIL-006] version 2 (task 1 changed after the first measurement)
|
||||||
|
- Checklist used: [QC-MIL-001]
|
||||||
|
- Scope: full review
|
||||||
|
- Language and domain: en / it (the artifact's Metadata rows)
|
||||||
|
- Language reviewer: S01 (confirmed criteria 3, 5 and 9 of QC-LANG-001 in chat on 2026-10-28)
|
||||||
|
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`)
|
||||||
|
|
||||||
|
## Checklist Results
|
||||||
|
|
||||||
|
| # | Criterion | Status | Evidence/Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | A concrete deliverable is defined for every gate | Pass | Deliverable names the changed functions, the one-product-per-visit behaviour, a Docker run with CPS and log, and the updated README and wiki. |
|
||||||
|
| 2 | Explicit Go/No-Go criteria are stated for each gate | Pass | Five checkable Go criteria; measured in the run: 1 failed purchase of 49 (about 2 percent, below 5), CPS 29 (above 16.6). |
|
||||||
|
| 3 | Dependencies on other milestones are explicitly mapped | Pass | Rechecked after the change of task 1; unchanged from RC-014. |
|
||||||
|
| 4 | Each milestone is traceable to a Business Case objective or KPI | Pass | Rechecked after the change of task 1; unchanged from RC-014. |
|
||||||
|
| 5 | Milestone owner and approving reviewer are identified | Pass | Rechecked after the change of task 1; unchanged from RC-014. |
|
||||||
|
| 6 | Milestone has a defined target date consistent with project constraints | Pass | Rechecked after the change of task 1; unchanged from RC-014. |
|
||||||
|
|
||||||
|
## Language and Domain Results
|
||||||
|
|
||||||
|
| # | Criterion | Status | Evidence/Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | The Metadata table has a `Language` row and a `Domain` row, and neither is a placeholder | Pass | Metadata has Language en and Domain it. |
|
||||||
|
| 2 | `Language` is a BCP 47 code and `Domain` is a value from the registry's domain list | Pass | en is a BCP 47 code and it is in the registry domain list. |
|
||||||
|
| 3 | The content (prose and table cells) is written in the stated language | Pass | Confirmed by S01 in chat on 2026-10-28 (named human reviewer). Content is English. |
|
||||||
|
| 4 | The register matches the one the registry gives for the artifact type | Pass | Register: IT Executive English. |
|
||||||
|
| 5 | Domain terms are the PO terms of the domain's dictionary, with no synonyms | Pass | Confirmed by S01 in chat on 2026-10-28 (named human reviewer). Ordinary IT terms; no dictionary exists or is needed. |
|
||||||
|
| 6 | Metadata keys, section headings, IDs and statuses are in English | Pass | Headings, keys, IDs and statuses are English. |
|
||||||
|
| 7 | No translated twin (`<name>.<language>.md`) exists beside the document | Pass | No translated twin exists. |
|
||||||
|
| 8 | A change of language or domain since the previous accepted version has a Version History row and was reviewed again | N-A | No change of language or domain. |
|
||||||
|
| 9 | A reviewer competent in the domain, and in the language, has confirmed that the domain terms are used correctly | Pass | Confirmed by S01 in chat on 2026-10-28 (named human reviewer). S01 is competent in English and in IT. |
|
||||||
|
| 10 | Abbreviations are spelled out on first use, in the stated language | Pass | Abbreviations are spelled out at first use. |
|
||||||
|
|
||||||
|
## Overall Verdict
|
||||||
|
|
||||||
|
Go - All Mandatory criteria pass; the language criteria for the amended text were confirmed by S01 in chat. Stand-in review, not independent.
|
||||||
|
|
||||||
|
## Action Items
|
||||||
|
|
||||||
|
| Action | Owner | Due |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| None | S01 | - |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[MIL-006]: ../../milestones/mil-006-purchase-strategy.md
|
||||||
|
[QC-MIL-001]: ../../../framework/qc/artifacts/qc-milestones-gateways.md
|
||||||
|
[QC-LANG-001]: ../../../framework/qc/artifacts/qc-language-domain.md
|
||||||
|
[375cc0b]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/048-the-coockie-factory/commit/375cc0b5f3919b00daeb2f8f5fcc81de25acfc4c
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# SQA Review Record
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
| Key | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| ID | RC-017 |
|
||||||
|
| CrossReference | [MIL-006-SRC], [QC-PY-001] |
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
| Date | Status | Author | Reviewer | Change | Commit |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| 2026-10-28 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | [375cc0b] |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Artifact Under Review
|
||||||
|
|
||||||
|
- Instance reviewed: Source code of MIL-006 (store, run_bot, selenium_game, constants, tests) ([MIL-006-SRC])
|
||||||
|
- Checklist used: [QC-PY-001]
|
||||||
|
- Scope: full review
|
||||||
|
- Language and domain: n/a (technical type)
|
||||||
|
- Language reviewer: none
|
||||||
|
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`)
|
||||||
|
|
||||||
|
## Checklist Results
|
||||||
|
|
||||||
|
| # | Criterion | Status | Evidence/Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | Packages, modules, functions, variables, classes and constants follow PEP 8 casing (`snake_case`, `PascalCase`, `UPPER_SNAKE`) | Pass | Checked in the diff of branch mil-006-purchase-strategy; 111 tests, ruff check, ruff format and mypy --strict pass; Selenium stays the only runtime dependency. |
|
||||||
|
| 2 | Names state purpose in the domain's language; no unexplained abbreviations, no single-letter names outside tiny scopes | Pass | Checked in the diff of branch mil-006-purchase-strategy; 111 tests, ruff check, ruff format and mypy --strict pass; Selenium stays the only runtime dependency. |
|
||||||
|
| 3 | Code is produced by the project's formatter and passes its linter with no unexplained suppressions | Pass | Checked in the diff of branch mil-006-purchase-strategy; 111 tests, ruff check, ruff format and mypy --strict pass; Selenium stays the only runtime dependency. |
|
||||||
|
| 4 | Every function and method signature is type-annotated, including `-> None` | Pass | Checked in the diff of branch mil-006-purchase-strategy; 111 tests, ruff check, ruff format and mypy --strict pass; Selenium stays the only runtime dependency. |
|
||||||
|
| 5 | No bare `except:`, no swallowed exceptions; specific exceptions are raised and the cause is kept (`raise ... from`) | Pass | Checked in the diff of branch mil-006-purchase-strategy; 111 tests, ruff check, ruff format and mypy --strict pass; Selenium stays the only runtime dependency. |
|
||||||
|
| 6 | No mutable default arguments and no shadowed builtins | Pass | Checked in the diff of branch mil-006-purchase-strategy; 111 tests, ruff check, ruff format and mypy --strict pass; Selenium stays the only runtime dependency. |
|
||||||
|
| 7 | Files, locks and connections are managed with context managers | Pass | Checked in the diff of branch mil-006-purchase-strategy; 111 tests, ruff check, ruff format and mypy --strict pass; Selenium stays the only runtime dependency. |
|
||||||
|
| 8 | Public modules, classes and functions have docstrings that say what, not how | Pass | Checked in the diff of branch mil-006-purchase-strategy; 111 tests, ruff check, ruff format and mypy --strict pass; Selenium stays the only runtime dependency. |
|
||||||
|
| 9 | Logging uses `logging`, not `print`; no secrets or personal data in log output | Pass | Checked in the diff of branch mil-006-purchase-strategy; 111 tests, ruff check, ruff format and mypy --strict pass; Selenium stays the only runtime dependency. |
|
||||||
|
| 10 | Classes and operations trace to the Design Class Diagram they implement; deviations are recorded | Pass | Checked in the diff of branch mil-006-purchase-strategy; 111 tests, ruff check, ruff format and mypy --strict pass; Selenium stays the only runtime dependency. |
|
||||||
|
| 11 | Tests exist for new behaviour, are named for the behaviour, and do not depend on order or the network | Pass | Checked in the diff of branch mil-006-purchase-strategy; 111 tests, ruff check, ruff format and mypy --strict pass; Selenium stays the only runtime dependency. |
|
||||||
|
| 12 | Type checker runs in strict mode without errors; `Any` is justified in a comment | Pass | Checked in the diff of branch mil-006-purchase-strategy; 111 tests, ruff check, ruff format and mypy --strict pass; Selenium stays the only runtime dependency. |
|
||||||
|
| 13 | Dependencies are declared and pinned in the project's dependency file, none unused | Pass | Checked in the diff of branch mil-006-purchase-strategy; 111 tests, ruff check, ruff format and mypy --strict pass; Selenium stays the only runtime dependency. |
|
||||||
|
| 14 | Each piece of knowledge (a business rule, constant, format, validation or query) is defined in one place; code is merged only where it expresses the same knowledge, not where it merely looks alike | Pass | Checked in the diff of branch mil-006-purchase-strategy; 111 tests, ruff check, ruff format and mypy --strict pass; Selenium stays the only runtime dependency. |
|
||||||
|
| 15 | Imports point inward: business-rule modules import no framework, database, UI or delivery-mechanism code, and there are no import cycles | Pass | Checked in the diff of branch mil-006-purchase-strategy; 111 tests, ruff check, ruff format and mypy --strict pass; Selenium stays the only runtime dependency. |
|
||||||
|
| 16 | SOLID holds: a class or module has one reason to change; new behaviour is added by extension, not by editing a type test in several places; subclasses honour the contract of their base; protocols are narrow; high-level code depends on protocols or abstract types and receives its implementations | Pass | Checked in the diff of branch mil-006-purchase-strategy; 111 tests, ruff check, ruff format and mypy --strict pass; Selenium stays the only runtime dependency. |
|
||||||
|
| 17 | Source is laid out in the layers and folders of the clean architecture folder structure (`domain`, `application`, `infrastructure`, `presentation` packages under `src/`, and `unit`, `integration` and `e2e` under `tests/`), in `snake_case`, and imports between the layers point inward | Pass | Checked in the diff of branch mil-006-purchase-strategy; 111 tests, ruff check, ruff format and mypy --strict pass; Selenium stays the only runtime dependency. |
|
||||||
|
| 18 | File and folder names are English, contain no space and no character outside letters, digits, `-`, `_` and `.`, and follow the casing of the language for files | Pass | Checked in the diff of branch mil-006-purchase-strategy; 111 tests, ruff check, ruff format and mypy --strict pass; Selenium stays the only runtime dependency. |
|
||||||
|
|
||||||
|
## Overall Verdict
|
||||||
|
|
||||||
|
Go - All Mandatory criteria pass. Stand-in review, not independent.
|
||||||
|
|
||||||
|
## Action Items
|
||||||
|
|
||||||
|
| Action | Owner | Due |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| None | S01 | - |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[MIL-006-SRC]: ../../../src/cookie_clicker_bot
|
||||||
|
[QC-PY-001]: ../../../framework/qc/code/qc-programming-python.md
|
||||||
|
[375cc0b]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/048-the-coockie-factory/commit/375cc0b5f3919b00daeb2f8f5fcc81de25acfc4c
|
||||||
@@ -9,8 +9,8 @@
|
|||||||
## Version History
|
## Version History
|
||||||
| Date | Status | Author | Reviewer | Change | Commit |
|
| Date | Status | Author | Reviewer | Change | Commit |
|
||||||
| --- | --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- | --- |
|
||||||
| 2026-10-28 | Proposed | Jens Tirsvad Nielsen | AI as stand-in for S01 | Added RC-012 (MIL-003 source review) | [cb8e5ed] |
|
| 2026-10-28 | Proposed | Jens Tirsvad Nielsen | AI as stand-in for S01 | Added MIL-006, RC-014 and RC-015 | [638730d] |
|
||||||
| 2026-10-28 | Proposed | Jens Tirsvad Nielsen | AI as stand-in for S01 | Added RC-013 (MIL-004 source review) | [9a9ea53] |
|
| 2026-10-28 | Proposed | Jens Tirsvad Nielsen | AI as stand-in for S01 | Added RC-016 and RC-017 (MIL-006 version 2 and its source review) | [375cc0b] |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -26,12 +26,13 @@ updated whenever an artifact instance is created or reviewed.
|
|||||||
| --- | --- | --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- | --- | --- |
|
||||||
| [BC-001] | BC | en | it | - | [SA-001], [PP-001] | [RC-001] |
|
| [BC-001] | BC | en | it | - | [SA-001], [PP-001] | [RC-001] |
|
||||||
| [SA-001] | SA | en | it | [BC-001] | [PP-001] | [RC-002] |
|
| [SA-001] | SA | en | it | [BC-001] | [PP-001] | [RC-002] |
|
||||||
| [PP-001] | PP | en | it | [BC-001], [SA-001] | [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005] | [RC-003] |
|
| [PP-001] | PP | en | it | [BC-001], [SA-001] | [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006] | [RC-003], [RC-015] |
|
||||||
| [MIL-001] | MIL | en | it | [PP-001] | [MIL-002], [ADR-0001] | [RC-004], [RC-010] |
|
| [MIL-001] | MIL | en | it | [PP-001] | [MIL-002], [ADR-0001] | [RC-004], [RC-010] |
|
||||||
| [MIL-002] | MIL | en | it | [MIL-001] | [MIL-003] | [RC-005], [RC-011] |
|
| [MIL-002] | MIL | en | it | [MIL-001] | [MIL-003] | [RC-005], [RC-011] |
|
||||||
| [MIL-003] | MIL | en | it | [MIL-002] | [MIL-004] | [RC-006], [RC-012] |
|
| [MIL-003] | MIL | en | it | [MIL-002] | [MIL-004] | [RC-006], [RC-012] |
|
||||||
| [MIL-004] | MIL | en | it | [MIL-003] | [MIL-005] | [RC-007], [RC-013] |
|
| [MIL-004] | MIL | en | it | [MIL-003] | [MIL-005] | [RC-007], [RC-013] |
|
||||||
| [MIL-005] | MIL | en | it | [MIL-004] | - | [RC-008] |
|
| [MIL-005] | MIL | en | it | [MIL-004] | [MIL-006] | [RC-008] |
|
||||||
|
| [MIL-006] | MIL | en | it | [MIL-005] | - | [RC-014], [RC-016], [RC-017] |
|
||||||
| [ADR-0001] | ADR | - | - | [BC-001], [SA-001], [MIL-001] | - | [RC-009] |
|
| [ADR-0001] | ADR | - | - | [BC-001], [SA-001], [MIL-001] | - | [RC-009] |
|
||||||
|
|
||||||
## Concern Coverage
|
## Concern Coverage
|
||||||
@@ -63,6 +64,7 @@ updated whenever an artifact instance is created or reviewed.
|
|||||||
[MIL-003]: ../milestones/mil-003-upgrades-and-five-minute-run.md
|
[MIL-003]: ../milestones/mil-003-upgrades-and-five-minute-run.md
|
||||||
[MIL-004]: ../milestones/mil-004-screenshots-and-readme.md
|
[MIL-004]: ../milestones/mil-004-screenshots-and-readme.md
|
||||||
[MIL-005]: ../milestones/mil-005-wiki-and-release.md
|
[MIL-005]: ../milestones/mil-005-wiki-and-release.md
|
||||||
|
[MIL-006]: ../milestones/mil-006-purchase-strategy.md
|
||||||
[RC-001]: ./reviews/rc-001-business-case.md
|
[RC-001]: ./reviews/rc-001-business-case.md
|
||||||
[RC-002]: ./reviews/rc-002-stakeholder-analysis.md
|
[RC-002]: ./reviews/rc-002-stakeholder-analysis.md
|
||||||
[RC-003]: ./reviews/rc-003-project-plan.md
|
[RC-003]: ./reviews/rc-003-project-plan.md
|
||||||
@@ -75,6 +77,11 @@ updated whenever an artifact instance is created or reviewed.
|
|||||||
[RC-010]: ./reviews/rc-010-mil-001-source.md
|
[RC-010]: ./reviews/rc-010-mil-001-source.md
|
||||||
[RC-011]: ./reviews/rc-011-mil-002-source.md
|
[RC-011]: ./reviews/rc-011-mil-002-source.md
|
||||||
[RC-012]: ./reviews/rc-012-mil-003-source.md
|
[RC-012]: ./reviews/rc-012-mil-003-source.md
|
||||||
[cb8e5ed]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/048-the-coockie-factory/commit/cb8e5edfbf5acd1d0553568c11be7bbb4c5e5e1c
|
|
||||||
[RC-013]: ./reviews/rc-013-mil-004-source.md
|
[RC-013]: ./reviews/rc-013-mil-004-source.md
|
||||||
[9a9ea53]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/048-the-coockie-factory/commit/9a9ea53cc17d0d4acb258855088ddf45b48b26ea
|
[9a9ea53]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/048-the-coockie-factory/commit/9a9ea53cc17d0d4acb258855088ddf45b48b26ea
|
||||||
|
[RC-014]: ./reviews/rc-014-mil-006-purchase-strategy.md
|
||||||
|
[RC-015]: ./reviews/rc-015-pp-001-version-2.md
|
||||||
|
[638730d]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/048-the-coockie-factory/commit/638730d95ba24ba77c6448730d7999aa3e90d863
|
||||||
|
[RC-016]: ./reviews/rc-016-mil-006-version-2.md
|
||||||
|
[RC-017]: ./reviews/rc-017-mil-006-source.md
|
||||||
|
[375cc0b]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/048-the-coockie-factory/commit/375cc0b5f3919b00daeb2f8f5fcc81de25acfc4c
|
||||||
|
|||||||
@@ -17,4 +17,4 @@ Denne wiki gennemgår de fem trin fra forelæsningen. Hver side har et skærmbil
|
|||||||
| [Trin 5: kørslen på fem minutter](Step-5-Five-Minute-Run-da) | Se løkken, resultatet og Docker-kørslen |
|
| [Trin 5: kørslen på fem minutter](Step-5-Five-Minute-Run-da) | Se løkken, resultatet og Docker-kørslen |
|
||||||
| [Appendiks](Appendix-Documentation-da) | Finde projektets dokumenter |
|
| [Appendiks](Appendix-Documentation-da) | Finde projektets dokumenter |
|
||||||
|
|
||||||
Resultatet af kørslen på skærmbillederne: **16,6 cookies i sekundet** efter 300 sekunder.
|
Resultatet af kørslen på skærmbillederne: **29 cookies i sekundet** efter 300 sekunder.
|
||||||
|
|||||||
@@ -17,4 +17,4 @@ Wiki ini menerangkan lima langkah kuliah. Setiap halaman mempunyai tangkapan skr
|
|||||||
| [Langkah 5: larian lima minit](Step-5-Five-Minute-Run-ms) | Melihat gelung, hasil dan larian Docker |
|
| [Langkah 5: larian lima minit](Step-5-Five-Minute-Run-ms) | Melihat gelung, hasil dan larian Docker |
|
||||||
| [Lampiran](Appendix-Documentation-ms) | Mencari dokumen projek |
|
| [Lampiran](Appendix-Documentation-ms) | Mencari dokumen projek |
|
||||||
|
|
||||||
Hasil larian dalam tangkapan skrin: **16.6 kuki sesaat** selepas 300 saat.
|
Hasil larian dalam tangkapan skrin: **29 kuki sesaat** selepas 300 saat.
|
||||||
|
|||||||
@@ -17,4 +17,4 @@ This wiki walks through the five lecture steps. Each page has a screenshot taken
|
|||||||
| [Step 5: the five-minute run](Step-5-Five-Minute-Run) | See the loop, the result and the Docker run |
|
| [Step 5: the five-minute run](Step-5-Five-Minute-Run) | See the loop, the result and the Docker run |
|
||||||
| [Appendix](Appendix-Documentation) | Find the project documents |
|
| [Appendix](Appendix-Documentation) | Find the project documents |
|
||||||
|
|
||||||
The result of the run shown in the screenshots: **16.6 cookies per second** after 300 seconds.
|
The result of the run shown in the screenshots: **29 cookies per second** after 300 seconds.
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def choose_upgrade(items: Sequence[StoreItem]) -> StoreItem | None:
|
|||||||
|
|
||||||
- **Hold beslutningen ren.** `choose_upgrade` tager almindelige `StoreItem`-værdier og returnerer én, så den testes uden noget spil (lige priser, intet overkommeligt, tom liste).
|
- **Hold beslutningen ren.** `choose_upgrade` tager almindelige `StoreItem`-værdier og returnerer én, så den testes uden noget spil (lige priser, intet overkommeligt, tom liste).
|
||||||
- **Bekræft resultatet, antag det ikke.** `buy_upgrade` tjekker, at det ejede antal (`productOwned<N>`) steg. At tjekke antallet af cookies først var forkert: antallet stiger, mens botten klikker.
|
- **Bekræft resultatet, antag det ikke.** `buy_upgrade` tjekker, at det ejede antal (`productOwned<N>`) steg. At tjekke antallet af cookies først var forkert: antallet stiger, mens botten klikker.
|
||||||
- **Siden halter bagefter.** Den rigtige kørsel viste, at cirka 40 procent af købsforsøgene mislykkes, fordi butikkens markeringer læses lige før spillet tegner om. Botten logger det og prøver igen ved næste butiksbesøg.
|
- **Siden halter bagefter.** Den første version tabte cirka 40 procent af sine køb: spillet opdaterer pris og ejet antal med det samme, men markeringen af, om der er råd, først ved næste gentegning, så et andet køb i samme besøg brugte en forældet markering. Højst ét produkt pr. butiksbesøg gav kun 1 fejl ud af 49.
|
||||||
- **Det rigtige spil finder det, falske ikke kan.** Genforsøget ved forældede elementer og tjekket af ejet antal kom begge fra kørsler i Docker.
|
- **Det rigtige spil finder det, falske ikke kan.** Genforsøget ved forældede elementer og tjekket af ejet antal kom begge fra kørsler i Docker.
|
||||||
|
|
||||||
Næste: [Trin 5: kørslen på fem minutter](Step-5-Five-Minute-Run-da)
|
Næste: [Trin 5: kørslen på fem minutter](Step-5-Five-Minute-Run-da)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def choose_upgrade(items: Sequence[StoreItem]) -> StoreItem | None:
|
|||||||
|
|
||||||
- **Pastikan keputusan bersih.** `choose_upgrade` menerima nilai `StoreItem` biasa dan memulangkan satu, jadi ia diuji tanpa permainan langsung (harga sama, tiada yang mampu dibeli, senarai kosong).
|
- **Pastikan keputusan bersih.** `choose_upgrade` menerima nilai `StoreItem` biasa dan memulangkan satu, jadi ia diuji tanpa permainan langsung (harga sama, tiada yang mampu dibeli, senarai kosong).
|
||||||
- **Sahkan hasilnya, jangan andaikan.** `buy_upgrade` menyemak bahawa jumlah dimiliki (`productOwned<N>`) bertambah. Menyemak bilangan kuki dahulu adalah salah: bilangan terus meningkat semasa bot mengklik.
|
- **Sahkan hasilnya, jangan andaikan.** `buy_upgrade` menyemak bahawa jumlah dimiliki (`productOwned<N>`) bertambah. Menyemak bilangan kuki dahulu adalah salah: bilangan terus meningkat semasa bot mengklik.
|
||||||
- **Halaman ketinggalan.** Larian sebenar menunjukkan kira-kira 40 peratus percubaan pembelian gagal kerana bendera kedai dibaca sebelum permainan melukis semula. Bot mencatatkannya dan mencuba lagi pada lawatan kedai seterusnya.
|
- **Halaman ketinggalan.** Keluaran pertama kehilangan kira-kira 40 peratus pembeliannya: permainan mengemas kini teks harga dan jumlah dimiliki serta-merta tetapi bendera mampu beli hanya pada lukisan semula seterusnya, jadi pembelian kedua dalam lawatan yang sama menggunakan bendera usang. Membeli paling banyak satu produk setiap lawatan mengurangkan kegagalan kepada 1 daripada 49.
|
||||||
- **Permainan sebenar menemui apa yang tidak dapat ditemui oleh yang palsu.** Percubaan semula elemen usang dan semakan jumlah dimiliki kedua-duanya datang daripada larian dalam Docker.
|
- **Permainan sebenar menemui apa yang tidak dapat ditemui oleh yang palsu.** Percubaan semula elemen usang dan semakan jumlah dimiliki kedua-duanya datang daripada larian dalam Docker.
|
||||||
|
|
||||||
Seterusnya: [Langkah 5: larian lima minit](Step-5-Five-Minute-Run-ms)
|
Seterusnya: [Langkah 5: larian lima minit](Step-5-Five-Minute-Run-ms)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def choose_upgrade(items: Sequence[StoreItem]) -> StoreItem | None:
|
|||||||
|
|
||||||
- **Keep the decision pure.** `choose_upgrade` takes plain `StoreItem` values and returns one, so it is tested with no game at all (ties, none affordable, empty list).
|
- **Keep the decision pure.** `choose_upgrade` takes plain `StoreItem` values and returns one, so it is tested with no game at all (ties, none affordable, empty list).
|
||||||
- **Confirm the result, do not assume it.** `buy_upgrade` checks that the owned amount (`productOwned<N>`) went up. Checking the cookie count first was wrong: the count keeps rising while the bot clicks.
|
- **Confirm the result, do not assume it.** `buy_upgrade` checks that the owned amount (`productOwned<N>`) went up. Checking the cookie count first was wrong: the count keeps rising while the bot clicks.
|
||||||
- **The page lags behind.** The live run showed that about 40 percent of the purchase attempts fail because the store flags are read just before the game redraws. The bot logs it and tries again at the next store check.
|
- **The page lags behind.** The first release lost about 40 percent of its purchases: the game updates the price and owned text at once but the affordable flag only at its next redraw, so a second purchase in the same visit used a stale flag. Buying at most one product per store visit cut the failures to 1 of 49.
|
||||||
- **The real game finds what fakes cannot.** The stale-element retry and the owned-amount check both came from running in Docker.
|
- **The real game finds what fakes cannot.** The stale-element retry and the owned-amount check both came from running in Docker.
|
||||||
|
|
||||||
Next: [Step 5: the five-minute run](Step-5-Five-Minute-Run)
|
Next: [Step 5: the five-minute run](Step-5-Five-Minute-Run)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
🌐 [English](Step-5-Five-Minute-Run) · **Dansk** · [Bahasa Melayu](Step-5-Five-Minute-Run-ms)
|
🌐 [English](Step-5-Five-Minute-Run) · **Dansk** · [Bahasa Melayu](Step-5-Five-Minute-Run-ms)
|
||||||
|
|
||||||
`run_bot` spiller i 300 sekunder i én løkke: en byge på 20 klik, og hvert 5. sekund et besøg i butikken. Til sidst læser `get_cps` cookies i sekundet. Kørslen på skærmbilledet nåede **16,6 cookies i sekundet** med 26 markører og 14 bedstemødre.
|
`run_bot` spiller i 300 sekunder i én løkke: en byge på 20 klik, og hvert sekund ét besøg i butikken: først opgraderingerne, så højst ét produkt. Til sidst læser `get_cps` cookies i sekundet. Kørslen på skærmbilledet nåede **29 cookies i sekundet** med 29 markører, 17 bedstemødre og 2 opgraderinger (den første version nåede 16,6).
|
||||||
|
|
||||||
**Skærmbillede**
|
**Skærmbillede**
|
||||||
|
|
||||||
@@ -24,4 +24,4 @@ while now - start < duration_seconds:
|
|||||||
- **Én løkke, ingen tråde.** Den tidligere løsning brugte to tråde og en ekstra pakke. Én løkke er lettere at læse, kræver ingen ekstra afhængighed og er let at teste.
|
- **Én løkke, ingen tråde.** Den tidligere løsning brugte to tråde og en ekstra pakke. Én løkke er lettere at læse, kræver ingen ekstra afhængighed og er let at teste.
|
||||||
- **Injicér uret.** `run_bot` får et `Clock`, så et falsk ur tester en kørsel på 300 sekunder på millisekunder.
|
- **Injicér uret.** `run_bot` får et `Clock`, så et falsk ur tester en kørsel på 300 sekunder på millisekunder.
|
||||||
- **Kør GUI'en i Docker.** Headless Chromium i en container tager aldrig din mus eller dit tastatur, og skærmbillederne og loggen kommer ud som filer.
|
- **Kør GUI'en i Docker.** Headless Chromium i en container tager aldrig din mus eller dit tastatur, og skærmbillederne og loggen kommer ud som filer.
|
||||||
- **Videre derfra.** Køb efter pris pr. cookie i sekundet, køb opgraderingerne og klik på den gyldne cookie: hver er en ny funktion bag de samme porte.
|
- **Videre derfra.** Køb efter pris pr. cookie i sekundet og klik på den gyldne cookie: hver er en ny funktion bag de samme porte.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
🌐 [English](Step-5-Five-Minute-Run) · [Dansk](Step-5-Five-Minute-Run-da) · **Bahasa Melayu**
|
🌐 [English](Step-5-Five-Minute-Run) · [Dansk](Step-5-Five-Minute-Run-da) · **Bahasa Melayu**
|
||||||
|
|
||||||
`run_bot` bermain selama 300 saat dalam satu gelung: satu rentetan 20 klik, kemudian setiap 5 saat satu lawatan ke kedai. Pada akhirnya `get_cps` membaca kuki sesaat. Larian dalam tangkapan skrin mencapai **16.6 kuki sesaat** dengan 26 kursor dan 14 nenek.
|
`run_bot` bermain selama 300 saat dalam satu gelung: satu rentetan 20 klik, kemudian setiap saat satu lawatan ke kedai: peti peningkatan dahulu, kemudian paling banyak satu produk. Pada akhirnya `get_cps` membaca kuki sesaat. Larian dalam tangkapan skrin mencapai **29 kuki sesaat** dengan 29 kursor, 17 nenek dan 2 peningkatan (keluaran pertama mencapai 16.6).
|
||||||
|
|
||||||
**Tangkapan skrin**
|
**Tangkapan skrin**
|
||||||
|
|
||||||
@@ -24,4 +24,4 @@ while now - start < duration_seconds:
|
|||||||
- **Satu gelung, tiada thread.** Penyelesaian terdahulu menggunakan dua thread dan satu pakej tambahan. Satu gelung lebih mudah dibaca, tidak memerlukan kebergantungan tambahan dan mudah diuji.
|
- **Satu gelung, tiada thread.** Penyelesaian terdahulu menggunakan dua thread dan satu pakej tambahan. Satu gelung lebih mudah dibaca, tidak memerlukan kebergantungan tambahan dan mudah diuji.
|
||||||
- **Suntik jam.** `run_bot` menerima `Clock`, jadi jam palsu menguji larian 300 saat dalam milisaat.
|
- **Suntik jam.** `run_bot` menerima `Clock`, jadi jam palsu menguji larian 300 saat dalam milisaat.
|
||||||
- **Jalankan GUI dalam Docker.** Chromium tanpa kepala dalam bekas tidak pernah mengambil tetikus atau papan kekunci anda, dan tangkapan skrin serta log keluar sebagai fail.
|
- **Jalankan GUI dalam Docker.** Chromium tanpa kepala dalam bekas tidak pernah mengambil tetikus atau papan kekunci anda, dan tangkapan skrin serta log keluar sebagai fail.
|
||||||
- **Langkah seterusnya.** Beli mengikut kos setiap kuki sesaat, beli peningkatan dan klik kuki emas: setiap satu ialah fungsi baharu di belakang port yang sama.
|
- **Langkah seterusnya.** Beli mengikut kos setiap kuki sesaat dan klik kuki emas: setiap satu ialah fungsi baharu di belakang port yang sama.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
🌐 **English** · [Dansk](Step-5-Five-Minute-Run-da) · [Bahasa Melayu](Step-5-Five-Minute-Run-ms)
|
🌐 **English** · [Dansk](Step-5-Five-Minute-Run-da) · [Bahasa Melayu](Step-5-Five-Minute-Run-ms)
|
||||||
|
|
||||||
`run_bot` plays for 300 seconds in one loop: a burst of 20 clicks, then every 5 seconds a visit to the store. At the end `get_cps` reads the cookies per second. The run in the screenshot reached **16.6 cookies per second** with 26 cursors and 14 grandmas.
|
`run_bot` plays for 300 seconds in one loop: a burst of 20 clicks, then every second one visit to the store: upgrade crates first, then at most one product. At the end `get_cps` reads the cookies per second. The run in the screenshot reached **29 cookies per second** with 29 cursors, 17 grandmas and 2 upgrades (the first release reached 16.6).
|
||||||
|
|
||||||
**Screenshot**
|
**Screenshot**
|
||||||
|
|
||||||
@@ -24,4 +24,4 @@ while now - start < duration_seconds:
|
|||||||
- **One loop, no threads.** The earlier solution used two threads and an extra package. One loop is easier to read, needs no extra dependency and is easy to test.
|
- **One loop, no threads.** The earlier solution used two threads and an extra package. One loop is easier to read, needs no extra dependency and is easy to test.
|
||||||
- **Inject the clock.** `run_bot` receives a `Clock`, so a fake clock tests a 300 second run in milliseconds.
|
- **Inject the clock.** `run_bot` receives a `Clock`, so a fake clock tests a 300 second run in milliseconds.
|
||||||
- **Run the GUI in Docker.** Headless Chromium in a container never takes your mouse or keyboard, and the screenshots and the log come out as files.
|
- **Run the GUI in Docker.** Headless Chromium in a container never takes your mouse or keyboard, and the screenshots and the log come out as files.
|
||||||
- **Where to go next.** Buy by cost per cookie per second, buy the upgrade crates, and click the golden cookie: each is a new function behind the same ports.
|
- **Where to go next.** Buy by cost per cookie per second and click the golden cookie: each is a new function behind the same ports.
|
||||||
|
|||||||
@@ -20,6 +20,13 @@ class GamePort(Protocol):
|
|||||||
"""
|
"""
|
||||||
...
|
...
|
||||||
|
|
||||||
|
def has_element(self, element_id: str) -> bool:
|
||||||
|
"""@brief Tell whether the element is on the page now.
|
||||||
|
@param element_id The HTML id of the element.
|
||||||
|
@return True when the page holds an element with the id.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
def click_element(self, element_id: str) -> None:
|
def click_element(self, element_id: str) -> None:
|
||||||
"""@brief Click the element the way a user does.
|
"""@brief Click the element the way a user does.
|
||||||
@param element_id The HTML id of the element.
|
@param element_id The HTML id of the element.
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from cookie_clicker_bot.application.ports.clock import Clock
|
|||||||
from cookie_clicker_bot.application.ports.game_port import GamePort
|
from cookie_clicker_bot.application.ports.game_port import GamePort
|
||||||
from cookie_clicker_bot.application.use_cases.bot_core import click_cookie
|
from cookie_clicker_bot.application.use_cases.bot_core import click_cookie
|
||||||
from cookie_clicker_bot.application.use_cases.store import (
|
from cookie_clicker_bot.application.use_cases.store import (
|
||||||
|
buy_available_upgrades,
|
||||||
buy_upgrade,
|
buy_upgrade,
|
||||||
get_cps,
|
get_cps,
|
||||||
get_store_items,
|
get_store_items,
|
||||||
@@ -23,18 +24,22 @@ from cookie_clicker_bot.domain.store_item import choose_upgrade
|
|||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _buy_while_affordable(game: GamePort) -> int:
|
def _buy_best_product(game: GamePort) -> bool:
|
||||||
"""@brief Buy the best affordable product until none is left or a purchase fails."""
|
"""@brief Buy the best affordable product, at most one, from a fresh read.
|
||||||
bought = 0
|
|
||||||
while (item := choose_upgrade(get_store_items(game))) is not None:
|
The game updates the owned amount at once but the affordable flags only at
|
||||||
if not buy_upgrade(game, item):
|
its next draw, so a second purchase in the same visit would read stale flags.
|
||||||
_logger.warning(
|
"""
|
||||||
"Purchase of product %d did not raise the owned amount", item.index
|
item = choose_upgrade(get_store_items(game))
|
||||||
)
|
if item is None:
|
||||||
break
|
return False
|
||||||
bought += 1
|
if not buy_upgrade(game, item):
|
||||||
_logger.info("Bought product %d for %d cookies", item.index, item.price)
|
_logger.warning(
|
||||||
return bought
|
"Purchase of product %d did not raise the owned amount", item.index
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
_logger.info("Bought product %d for %d cookies", item.index, item.price)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def run_bot(
|
def run_bot(
|
||||||
@@ -45,7 +50,8 @@ def run_bot(
|
|||||||
"""@brief Click the cookie and buy upgrades until the time is up.
|
"""@brief Click the cookie and buy upgrades until the time is up.
|
||||||
|
|
||||||
One loop: a burst of clicks, then, every store check interval, a visit to
|
One loop: a burst of clicks, then, every store check interval, a visit to
|
||||||
the store that buys the most expensive affordable product until none is.
|
the store that buys the affordable upgrades, cheapest first, and then at
|
||||||
|
most one product, the most expensive affordable one.
|
||||||
|
|
||||||
@param game The game page to operate.
|
@param game The game page to operate.
|
||||||
@param clock The clock that measures the run.
|
@param clock The clock that measures the run.
|
||||||
@@ -62,7 +68,8 @@ def run_bot(
|
|||||||
click_cookie(game, CLICKS_PER_BURST)
|
click_cookie(game, CLICKS_PER_BURST)
|
||||||
now = clock.monotonic()
|
now = clock.monotonic()
|
||||||
if now >= next_store_check:
|
if now >= next_store_check:
|
||||||
_buy_while_affordable(game)
|
buy_available_upgrades(game)
|
||||||
|
_buy_best_product(game)
|
||||||
next_store_check = now + STORE_CHECK_INTERVAL_SECONDS
|
next_store_check = now + STORE_CHECK_INTERVAL_SECONDS
|
||||||
_logger.info("%.0f of %s seconds played", now - start, duration_seconds)
|
_logger.info("%.0f of %s seconds played", now - start, duration_seconds)
|
||||||
cps = get_cps(game)
|
cps = get_cps(game)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
@brief Lecture step 4: read the store, buy a product and read the cookies per second.
|
@brief Lecture step 4: read the store, buy a product and read the cookies per second.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from cookie_clicker_bot.application.ports.game_port import GamePort
|
from cookie_clicker_bot.application.ports.game_port import GamePort
|
||||||
@@ -10,12 +11,16 @@ from cookie_clicker_bot.constants import (
|
|||||||
CPS_ID,
|
CPS_ID,
|
||||||
CPS_SEPARATOR,
|
CPS_SEPARATOR,
|
||||||
ENABLED_CLASS,
|
ENABLED_CLASS,
|
||||||
|
FIRST_UPGRADE_ID,
|
||||||
OWNED_CHECK_ATTEMPTS,
|
OWNED_CHECK_ATTEMPTS,
|
||||||
PRODUCT_COUNT,
|
PRODUCT_COUNT,
|
||||||
PRODUCT_ID_PREFIX,
|
PRODUCT_ID_PREFIX,
|
||||||
PRODUCT_OWNED_ID_PREFIX,
|
PRODUCT_OWNED_ID_PREFIX,
|
||||||
PRODUCT_PRICE_ID_PREFIX,
|
PRODUCT_PRICE_ID_PREFIX,
|
||||||
|
REDRAW_CHECK_ATTEMPTS,
|
||||||
UNLOCKED_CLASS,
|
UNLOCKED_CLASS,
|
||||||
|
UPGRADE_BUY_LIMIT,
|
||||||
|
UPGRADE_IDENTITY_ATTRIBUTE,
|
||||||
)
|
)
|
||||||
from cookie_clicker_bot.domain.number_parsing import (
|
from cookie_clicker_bot.domain.number_parsing import (
|
||||||
parse_game_decimal,
|
parse_game_decimal,
|
||||||
@@ -23,6 +28,8 @@ from cookie_clicker_bot.domain.number_parsing import (
|
|||||||
)
|
)
|
||||||
from cookie_clicker_bot.domain.store_item import StoreItem
|
from cookie_clicker_bot.domain.store_item import StoreItem
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_store_items(game: GamePort) -> list[StoreItem]:
|
def get_store_items(game: GamePort) -> list[StoreItem]:
|
||||||
"""@brief Read the products that are visible in the store.
|
"""@brief Read the products that are visible in the store.
|
||||||
@@ -66,6 +73,48 @@ def buy_upgrade(game: GamePort, item: StoreItem) -> bool:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _first_upgrade_is_affordable(game: GamePort) -> bool:
|
||||||
|
if not game.has_element(FIRST_UPGRADE_ID):
|
||||||
|
return False
|
||||||
|
return (
|
||||||
|
ENABLED_CLASS in game.read_attribute(FIRST_UPGRADE_ID, CLASS_ATTRIBUTE).split()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _upgrade_was_replaced(game: GamePort, old_identity: str) -> bool:
|
||||||
|
"""@brief Wait until the first crate shows another upgrade or is gone."""
|
||||||
|
for _ in range(REDRAW_CHECK_ATTEMPTS):
|
||||||
|
if not game.has_element(FIRST_UPGRADE_ID):
|
||||||
|
return True
|
||||||
|
identity = game.read_attribute(FIRST_UPGRADE_ID, UPGRADE_IDENTITY_ATTRIBUTE)
|
||||||
|
if identity != old_identity:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def buy_available_upgrades(game: GamePort) -> int:
|
||||||
|
"""@brief Buy the upgrade crates the player can afford, cheapest first.
|
||||||
|
|
||||||
|
The crates are numbered by price and numbered again after each purchase, so
|
||||||
|
the cheapest one is always the first. The loop stops at the first crate that
|
||||||
|
is missing or unaffordable, at UPGRADE_BUY_LIMIT, or when a click leaves the
|
||||||
|
crate unchanged.
|
||||||
|
|
||||||
|
@param game The game page to operate.
|
||||||
|
@return How many upgrades were bought.
|
||||||
|
"""
|
||||||
|
bought = 0
|
||||||
|
while bought < UPGRADE_BUY_LIMIT and _first_upgrade_is_affordable(game):
|
||||||
|
identity = game.read_attribute(FIRST_UPGRADE_ID, UPGRADE_IDENTITY_ATTRIBUTE)
|
||||||
|
game.click_element(FIRST_UPGRADE_ID)
|
||||||
|
if not _upgrade_was_replaced(game, identity):
|
||||||
|
_logger.warning("Upgrade %s stayed in the store after the click", identity)
|
||||||
|
break
|
||||||
|
bought += 1
|
||||||
|
_logger.info("Bought upgrade %s", identity)
|
||||||
|
return bought
|
||||||
|
|
||||||
|
|
||||||
def get_cps(game: GamePort) -> Decimal:
|
def get_cps(game: GamePort) -> Decimal:
|
||||||
"""@brief Read the cookies per second the bakery makes now.
|
"""@brief Read the cookies per second the bakery makes now.
|
||||||
@param game The game page to read.
|
@param game The game page to read.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
RUN_DURATION_SECONDS: int = 300
|
RUN_DURATION_SECONDS: int = 300
|
||||||
|
|
||||||
## Seconds between two looks at the store while the bot keeps clicking.
|
## Seconds between two looks at the store while the bot keeps clicking.
|
||||||
STORE_CHECK_INTERVAL_SECONDS: int = 5
|
STORE_CHECK_INTERVAL_SECONDS: int = 1
|
||||||
|
|
||||||
## Seconds to wait for an element before the bot gives up.
|
## Seconds to wait for an element before the bot gives up.
|
||||||
ELEMENT_TIMEOUT_SECONDS: int = 20
|
ELEMENT_TIMEOUT_SECONDS: int = 20
|
||||||
@@ -44,6 +44,19 @@ PRODUCT_OWNED_ID_PREFIX: str = "productOwned"
|
|||||||
## Reads of the owned amount after a purchase click, as the game redraws late.
|
## Reads of the owned amount after a purchase click, as the game redraws late.
|
||||||
OWNED_CHECK_ATTEMPTS: int = 20
|
OWNED_CHECK_ATTEMPTS: int = 20
|
||||||
|
|
||||||
|
## Reads of a text after a purchase while the bot waits for the store to redraw.
|
||||||
|
REDRAW_CHECK_ATTEMPTS: int = 20
|
||||||
|
|
||||||
|
## Element id of the cheapest upgrade crate; the game numbers the crates by price
|
||||||
|
## and numbers them again after each purchase.
|
||||||
|
FIRST_UPGRADE_ID: str = "upgrade0"
|
||||||
|
|
||||||
|
## Attribute of a crate that holds the id of the upgrade it shows.
|
||||||
|
UPGRADE_IDENTITY_ATTRIBUTE: str = "data-id"
|
||||||
|
|
||||||
|
## Most upgrades the bot buys in one visit to the store.
|
||||||
|
UPGRADE_BUY_LIMIT: int = 50
|
||||||
|
|
||||||
## Times an action on the page is repeated after a stale or blocked element.
|
## Times an action on the page is repeated after a stale or blocked element.
|
||||||
CLICK_RETRIES: int = 3
|
CLICK_RETRIES: int = 3
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,15 @@ class SeleniumGame:
|
|||||||
"""
|
"""
|
||||||
_retry_on_page_change(lambda: self._click_once(element_id))
|
_retry_on_page_change(lambda: self._click_once(element_id))
|
||||||
|
|
||||||
|
def has_element(self, element_id: str) -> bool:
|
||||||
|
"""@brief Tell whether the element is on the page now.
|
||||||
|
@param element_id The HTML id of the element.
|
||||||
|
@return True when the page holds an element with the id.
|
||||||
|
"""
|
||||||
|
return _retry_on_page_change(
|
||||||
|
lambda: bool(self._driver.find_elements(By.ID, element_id))
|
||||||
|
)
|
||||||
|
|
||||||
def _click_once(self, element_id: str) -> None:
|
def _click_once(self, element_id: str) -> None:
|
||||||
element = WebDriverWait(self._driver, ELEMENT_TIMEOUT_SECONDS).until(
|
element = WebDriverWait(self._driver, ELEMENT_TIMEOUT_SECONDS).until(
|
||||||
expected_conditions.element_to_be_clickable((By.ID, element_id))
|
expected_conditions.element_to_be_clickable((By.ID, element_id))
|
||||||
|
|||||||
@@ -76,6 +76,12 @@ class FakeDriver:
|
|||||||
self.screenshots.append(filename)
|
self.screenshots.append(filename)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def find_elements(self, by: str, value: str) -> list[FakeElement]:
|
||||||
|
self.lookups.append((by, value))
|
||||||
|
if self.lookup_failures:
|
||||||
|
raise self.lookup_failures.pop(0)
|
||||||
|
return [self.elements[value]] if value in self.elements else []
|
||||||
|
|
||||||
def quit(self) -> None:
|
def quit(self) -> None:
|
||||||
self.quit_calls += 1
|
self.quit_calls += 1
|
||||||
|
|
||||||
@@ -186,6 +192,20 @@ def test_read_attribute_raises_after_the_retry_limit() -> None:
|
|||||||
make_game(driver).read_attribute("product0", "class")
|
make_game(driver).read_attribute("product0", "class")
|
||||||
|
|
||||||
|
|
||||||
|
def test_has_element_tells_whether_the_id_is_on_the_page() -> None:
|
||||||
|
game = make_game(FakeDriver({"upgrade0": FakeElement()}))
|
||||||
|
|
||||||
|
assert game.has_element("upgrade0") is True
|
||||||
|
assert game.has_element("upgrade1") is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_has_element_retries_after_a_stale_element() -> None:
|
||||||
|
driver = FakeDriver({"upgrade0": FakeElement()})
|
||||||
|
driver.lookup_failures = [StaleElementReferenceException()] * CLICK_RETRIES
|
||||||
|
|
||||||
|
assert make_game(driver).has_element("upgrade0") is True
|
||||||
|
|
||||||
|
|
||||||
def test_save_screenshot_creates_the_folder_and_writes_the_file(
|
def test_save_screenshot_creates_the_folder_and_writes_the_file(
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -14,13 +14,19 @@ class FakeGame:
|
|||||||
attributes: dict[tuple[str, str], str] | None = None,
|
attributes: dict[tuple[str, str], str] | None = None,
|
||||||
on_click: Callable[[str], None] | None = None,
|
on_click: Callable[[str], None] | None = None,
|
||||||
text_sequences: dict[str, list[str]] | None = None,
|
text_sequences: dict[str, list[str]] | None = None,
|
||||||
|
attribute_sequences: dict[tuple[str, str], list[str]] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.texts: dict[str, str] = texts or {}
|
self.texts: dict[str, str] = texts or {}
|
||||||
self.attributes: dict[tuple[str, str], str] = attributes or {}
|
self.attributes: dict[tuple[str, str], str] = attributes or {}
|
||||||
self.on_click = on_click
|
self.on_click = on_click
|
||||||
## Texts returned one per read, before the plain text applies.
|
## Texts returned one per read, before the plain text applies.
|
||||||
self.text_sequences: dict[str, list[str]] = text_sequences or {}
|
self.text_sequences: dict[str, list[str]] = text_sequences or {}
|
||||||
|
## Attribute values returned one per read, before the plain value applies.
|
||||||
|
self.attribute_sequences: dict[tuple[str, str], list[str]] = (
|
||||||
|
attribute_sequences or {}
|
||||||
|
)
|
||||||
self.reads: dict[str, int] = {}
|
self.reads: dict[str, int] = {}
|
||||||
|
self.attribute_reads: dict[tuple[str, str], int] = {}
|
||||||
self.opened: list[str] = []
|
self.opened: list[str] = []
|
||||||
self.waited: list[str] = []
|
self.waited: list[str] = []
|
||||||
self.clicked: list[str] = []
|
self.clicked: list[str] = []
|
||||||
@@ -32,6 +38,12 @@ class FakeGame:
|
|||||||
def wait_for_element(self, element_id: str) -> None:
|
def wait_for_element(self, element_id: str) -> None:
|
||||||
self.waited.append(element_id)
|
self.waited.append(element_id)
|
||||||
|
|
||||||
|
def has_element(self, element_id: str) -> bool:
|
||||||
|
"""An element exists when a text or an attribute is defined for it."""
|
||||||
|
return element_id in self.texts or any(
|
||||||
|
known == element_id for known, _ in self.attributes
|
||||||
|
)
|
||||||
|
|
||||||
def click_element(self, element_id: str) -> None:
|
def click_element(self, element_id: str) -> None:
|
||||||
self.clicked.append(element_id)
|
self.clicked.append(element_id)
|
||||||
if self.on_click is not None:
|
if self.on_click is not None:
|
||||||
@@ -45,7 +57,12 @@ class FakeGame:
|
|||||||
return self.texts[element_id]
|
return self.texts[element_id]
|
||||||
|
|
||||||
def read_attribute(self, element_id: str, attribute: str) -> str:
|
def read_attribute(self, element_id: str, attribute: str) -> str:
|
||||||
return self.attributes.get((element_id, attribute), "")
|
key = (element_id, attribute)
|
||||||
|
self.attribute_reads[key] = self.attribute_reads.get(key, 0) + 1
|
||||||
|
queued = self.attribute_sequences.get(key)
|
||||||
|
if queued:
|
||||||
|
return queued.pop(0)
|
||||||
|
return self.attributes.get(key, "")
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
self.close_calls += 1
|
self.close_calls += 1
|
||||||
|
|||||||
@@ -9,5 +9,9 @@ def test_run_duration_is_five_minutes() -> None:
|
|||||||
assert constants.RUN_DURATION_SECONDS == 300
|
assert constants.RUN_DURATION_SECONDS == 300
|
||||||
|
|
||||||
|
|
||||||
|
def test_store_is_visited_every_second() -> None:
|
||||||
|
assert constants.STORE_CHECK_INTERVAL_SECONDS == 1
|
||||||
|
|
||||||
|
|
||||||
def test_store_check_happens_inside_the_run() -> None:
|
def test_store_check_happens_inside_the_run() -> None:
|
||||||
assert 0 < constants.STORE_CHECK_INTERVAL_SECONDS < constants.RUN_DURATION_SECONDS
|
assert 0 < constants.STORE_CHECK_INTERVAL_SECONDS < constants.RUN_DURATION_SECONDS
|
||||||
|
|||||||
@@ -13,11 +13,13 @@ from cookie_clicker_bot.constants import (
|
|||||||
CLICKS_PER_BURST,
|
CLICKS_PER_BURST,
|
||||||
COOKIE_COUNTER_ID,
|
COOKIE_COUNTER_ID,
|
||||||
CPS_ID,
|
CPS_ID,
|
||||||
|
FIRST_UPGRADE_ID,
|
||||||
PRODUCT_ID_PREFIX,
|
PRODUCT_ID_PREFIX,
|
||||||
PRODUCT_OWNED_ID_PREFIX,
|
PRODUCT_OWNED_ID_PREFIX,
|
||||||
PRODUCT_PRICE_ID_PREFIX,
|
PRODUCT_PRICE_ID_PREFIX,
|
||||||
RUN_DURATION_SECONDS,
|
RUN_DURATION_SECONDS,
|
||||||
STORE_CHECK_INTERVAL_SECONDS,
|
STORE_CHECK_INTERVAL_SECONDS,
|
||||||
|
UPGRADE_IDENTITY_ATTRIBUTE,
|
||||||
)
|
)
|
||||||
from cookie_clicker_bot.domain.errors import UnreadableNumberError
|
from cookie_clicker_bot.domain.errors import UnreadableNumberError
|
||||||
from tests.unit.fakes import FakeClock, FakeGame
|
from tests.unit.fakes import FakeClock, FakeGame
|
||||||
@@ -66,7 +68,7 @@ def test_run_bot_stops_after_the_duration() -> None:
|
|||||||
|
|
||||||
def test_run_bot_does_not_visit_the_store_before_the_interval() -> None:
|
def test_run_bot_does_not_visit_the_store_before_the_interval() -> None:
|
||||||
game = make_game()
|
game = make_game()
|
||||||
run_bot(game, FakeClock(step=1.0), STORE_CHECK_INTERVAL_SECONDS - 1)
|
run_bot(game, FakeClock(step=0.25), STORE_CHECK_INTERVAL_SECONDS * 0.75)
|
||||||
assert PRODUCT not in game.clicked
|
assert PRODUCT not in game.clicked
|
||||||
|
|
||||||
|
|
||||||
@@ -76,7 +78,60 @@ def test_run_bot_buys_at_the_interval() -> None:
|
|||||||
assert game.clicked.count(PRODUCT) == 1
|
assert game.clicked.count(PRODUCT) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_run_bot_stops_buying_when_nothing_is_affordable() -> None:
|
def test_run_bot_buys_crates_before_products() -> None:
|
||||||
|
game = make_game()
|
||||||
|
game.attributes[(FIRST_UPGRADE_ID, CLASS_ATTRIBUTE)] = "crate upgrade enabled"
|
||||||
|
game.attributes[(FIRST_UPGRADE_ID, UPGRADE_IDENTITY_ATTRIBUTE)] = "7"
|
||||||
|
previous = game.on_click
|
||||||
|
|
||||||
|
def click(element_id: str) -> None:
|
||||||
|
if element_id == FIRST_UPGRADE_ID:
|
||||||
|
game.attributes[(FIRST_UPGRADE_ID, UPGRADE_IDENTITY_ATTRIBUTE)] = "8"
|
||||||
|
game.attributes[(FIRST_UPGRADE_ID, CLASS_ATTRIBUTE)] = "crate disabled"
|
||||||
|
elif previous is not None:
|
||||||
|
previous(element_id)
|
||||||
|
|
||||||
|
game.on_click = click
|
||||||
|
run_bot(game, FakeClock(step=1.0), STORE_CHECK_INTERVAL_SECONDS)
|
||||||
|
assert [c for c in game.clicked if c in (FIRST_UPGRADE_ID, PRODUCT)] == [
|
||||||
|
FIRST_UPGRADE_ID,
|
||||||
|
PRODUCT,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_bot_buys_one_product_per_visit_and_the_next_at_the_next_visit(
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
game = FakeGame(
|
||||||
|
texts={
|
||||||
|
CPS_ID: "per second: 1",
|
||||||
|
f"{PRODUCT_PRICE_ID_PREFIX}0": "15",
|
||||||
|
f"{PRODUCT_PRICE_ID_PREFIX}1": "50",
|
||||||
|
f"{PRODUCT_OWNED_ID_PREFIX}0": "",
|
||||||
|
f"{PRODUCT_OWNED_ID_PREFIX}1": "",
|
||||||
|
},
|
||||||
|
attributes={
|
||||||
|
(f"{PRODUCT_ID_PREFIX}0", CLASS_ATTRIBUTE): "product unlocked enabled",
|
||||||
|
(f"{PRODUCT_ID_PREFIX}1", CLASS_ATTRIBUTE): "product unlocked enabled",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
visits: list[int] = []
|
||||||
|
|
||||||
|
def click(element_id: str) -> None:
|
||||||
|
if element_id.startswith(PRODUCT_ID_PREFIX):
|
||||||
|
index = element_id.removeprefix(PRODUCT_ID_PREFIX)
|
||||||
|
visits.append(int(index))
|
||||||
|
game.texts[f"{PRODUCT_OWNED_ID_PREFIX}{index}"] = "1"
|
||||||
|
game.attributes[(element_id, CLASS_ATTRIBUTE)] = "product unlocked"
|
||||||
|
|
||||||
|
game.on_click = click
|
||||||
|
with caplog.at_level("WARNING"):
|
||||||
|
run_bot(game, FakeClock(step=1.0), STORE_CHECK_INTERVAL_SECONDS * 2)
|
||||||
|
assert visits == [1, 0]
|
||||||
|
assert caplog.records == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_bot_buys_nothing_more_when_nothing_is_affordable() -> None:
|
||||||
game = make_game()
|
game = make_game()
|
||||||
run_bot(game, FakeClock(step=1.0), STORE_CHECK_INTERVAL_SECONDS * 3)
|
run_bot(game, FakeClock(step=1.0), STORE_CHECK_INTERVAL_SECONDS * 3)
|
||||||
assert game.clicked.count(PRODUCT) == 1
|
assert game.clicked.count(PRODUCT) == 1
|
||||||
@@ -85,7 +140,7 @@ def test_run_bot_stops_buying_when_nothing_is_affordable() -> None:
|
|||||||
def test_run_bot_stops_buying_when_a_purchase_fails() -> None:
|
def test_run_bot_stops_buying_when_a_purchase_fails() -> None:
|
||||||
game = make_game()
|
game = make_game()
|
||||||
game.on_click = None
|
game.on_click = None
|
||||||
run_bot(game, FakeClock(step=1.0), STORE_CHECK_INTERVAL_SECONDS + 1)
|
run_bot(game, FakeClock(step=1.0), STORE_CHECK_INTERVAL_SECONDS)
|
||||||
assert game.clicked.count(PRODUCT) == 1
|
assert game.clicked.count(PRODUCT) == 1
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from decimal import Decimal
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from cookie_clicker_bot.application.use_cases.store import (
|
from cookie_clicker_bot.application.use_cases.store import (
|
||||||
|
buy_available_upgrades,
|
||||||
buy_upgrade,
|
buy_upgrade,
|
||||||
get_cps,
|
get_cps,
|
||||||
get_store_items,
|
get_store_items,
|
||||||
@@ -15,10 +16,14 @@ from cookie_clicker_bot.constants import (
|
|||||||
CLASS_ATTRIBUTE,
|
CLASS_ATTRIBUTE,
|
||||||
COOKIE_COUNTER_ID,
|
COOKIE_COUNTER_ID,
|
||||||
CPS_ID,
|
CPS_ID,
|
||||||
|
FIRST_UPGRADE_ID,
|
||||||
OWNED_CHECK_ATTEMPTS,
|
OWNED_CHECK_ATTEMPTS,
|
||||||
PRODUCT_ID_PREFIX,
|
PRODUCT_ID_PREFIX,
|
||||||
PRODUCT_OWNED_ID_PREFIX,
|
PRODUCT_OWNED_ID_PREFIX,
|
||||||
PRODUCT_PRICE_ID_PREFIX,
|
PRODUCT_PRICE_ID_PREFIX,
|
||||||
|
REDRAW_CHECK_ATTEMPTS,
|
||||||
|
UPGRADE_BUY_LIMIT,
|
||||||
|
UPGRADE_IDENTITY_ATTRIBUTE,
|
||||||
)
|
)
|
||||||
from cookie_clicker_bot.domain.errors import UnreadableNumberError
|
from cookie_clicker_bot.domain.errors import UnreadableNumberError
|
||||||
from cookie_clicker_bot.domain.store_item import StoreItem
|
from cookie_clicker_bot.domain.store_item import StoreItem
|
||||||
@@ -126,7 +131,10 @@ def test_buy_upgrade_rejects_a_change_one_attempt_too_late() -> None:
|
|||||||
|
|
||||||
def test_buy_upgrade_ignores_the_cookie_count() -> None:
|
def test_buy_upgrade_ignores_the_cookie_count() -> None:
|
||||||
game = FakeGame(
|
game = FakeGame(
|
||||||
texts={f"{PRODUCT_OWNED_ID_PREFIX}3": "", COOKIE_COUNTER_ID: "100 cookies"}
|
texts={
|
||||||
|
f"{PRODUCT_OWNED_ID_PREFIX}3": "",
|
||||||
|
COOKIE_COUNTER_ID: "100 cookies",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
game.on_click = lambda _id: game.texts.update({COOKIE_COUNTER_ID: "90 cookies"})
|
game.on_click = lambda _id: game.texts.update({COOKIE_COUNTER_ID: "90 cookies"})
|
||||||
assert buy_upgrade(game, StoreItem(3, 15, True)) is False
|
assert buy_upgrade(game, StoreItem(3, 15, True)) is False
|
||||||
@@ -138,6 +146,95 @@ def test_buy_upgrade_propagates_an_unreadable_owned_amount() -> None:
|
|||||||
buy_upgrade(game, StoreItem(3, 15, True))
|
buy_upgrade(game, StoreItem(3, 15, True))
|
||||||
|
|
||||||
|
|
||||||
|
def _crate_game(identities: list[str], enabled: int) -> FakeGame:
|
||||||
|
"""Crates numbered from upgrade0; the first `enabled` crates are affordable."""
|
||||||
|
game = FakeGame()
|
||||||
|
crates = list(identities)
|
||||||
|
affordable_left = [enabled]
|
||||||
|
class_key = (FIRST_UPGRADE_ID, CLASS_ATTRIBUTE)
|
||||||
|
identity_key = (FIRST_UPGRADE_ID, UPGRADE_IDENTITY_ATTRIBUTE)
|
||||||
|
|
||||||
|
def render() -> None:
|
||||||
|
game.attributes.pop(class_key, None)
|
||||||
|
game.attributes.pop(identity_key, None)
|
||||||
|
if crates:
|
||||||
|
is_affordable = affordable_left[0] > 0
|
||||||
|
game.attributes[class_key] = (
|
||||||
|
"crate upgrade enabled" if is_affordable else "crate upgrade disabled"
|
||||||
|
)
|
||||||
|
game.attributes[identity_key] = crates[0]
|
||||||
|
|
||||||
|
def click(element_id: str) -> None:
|
||||||
|
if element_id == FIRST_UPGRADE_ID:
|
||||||
|
crates.pop(0)
|
||||||
|
affordable_left[0] -= 1
|
||||||
|
render()
|
||||||
|
|
||||||
|
game.on_click = click
|
||||||
|
render()
|
||||||
|
return game
|
||||||
|
|
||||||
|
|
||||||
|
def test_buy_available_upgrades_buys_nothing_without_crates() -> None:
|
||||||
|
game = FakeGame()
|
||||||
|
assert buy_available_upgrades(game) == 0
|
||||||
|
assert game.clicked == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_buy_available_upgrades_buys_one_affordable_crate() -> None:
|
||||||
|
game = _crate_game(["7"], enabled=1)
|
||||||
|
assert buy_available_upgrades(game) == 1
|
||||||
|
assert game.clicked == [FIRST_UPGRADE_ID]
|
||||||
|
|
||||||
|
|
||||||
|
def test_buy_available_upgrades_buys_several_in_a_row() -> None:
|
||||||
|
game = _crate_game(["7", "8", "9", "10"], enabled=3)
|
||||||
|
assert buy_available_upgrades(game) == 3
|
||||||
|
assert game.clicked == [FIRST_UPGRADE_ID] * 3
|
||||||
|
|
||||||
|
|
||||||
|
def test_buy_available_upgrades_stops_at_a_disabled_first_crate() -> None:
|
||||||
|
game = _crate_game(["7", "8"], enabled=0)
|
||||||
|
assert buy_available_upgrades(game) == 0
|
||||||
|
assert game.clicked == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_buy_available_upgrades_is_bounded_by_the_limit() -> None:
|
||||||
|
count = UPGRADE_BUY_LIMIT + 5
|
||||||
|
game = _crate_game([str(i) for i in range(count)], enabled=count)
|
||||||
|
assert buy_available_upgrades(game) == UPGRADE_BUY_LIMIT
|
||||||
|
assert game.clicked == [FIRST_UPGRADE_ID] * UPGRADE_BUY_LIMIT
|
||||||
|
|
||||||
|
|
||||||
|
def test_buy_available_upgrades_waits_for_the_redraw() -> None:
|
||||||
|
game = _crate_game(["7", "8"], enabled=2)
|
||||||
|
key = (FIRST_UPGRADE_ID, UPGRADE_IDENTITY_ATTRIBUTE)
|
||||||
|
inner = game.on_click
|
||||||
|
|
||||||
|
def click_then_lag(element_id: str) -> None:
|
||||||
|
assert inner is not None
|
||||||
|
inner(element_id)
|
||||||
|
if len(game.clicked) == 1:
|
||||||
|
game.attribute_sequences[key] = ["7", "7"]
|
||||||
|
|
||||||
|
game.on_click = click_then_lag
|
||||||
|
assert buy_available_upgrades(game) == 2
|
||||||
|
assert game.clicked == [FIRST_UPGRADE_ID] * 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_buy_available_upgrades_stops_when_the_crate_never_changes() -> None:
|
||||||
|
game = FakeGame(
|
||||||
|
attributes={
|
||||||
|
(FIRST_UPGRADE_ID, CLASS_ATTRIBUTE): "crate upgrade enabled",
|
||||||
|
(FIRST_UPGRADE_ID, UPGRADE_IDENTITY_ATTRIBUTE): "7",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert buy_available_upgrades(game) == 0
|
||||||
|
assert game.clicked == [FIRST_UPGRADE_ID]
|
||||||
|
key = (FIRST_UPGRADE_ID, UPGRADE_IDENTITY_ATTRIBUTE)
|
||||||
|
assert game.attribute_reads[key] == 1 + REDRAW_CHECK_ATTEMPTS
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("text", "expected"),
|
("text", "expected"),
|
||||||
[
|
[
|
||||||
|
|||||||