Structuring Bots
Retrieving information from the screen is rarely the final goal. The purpose of guirecognizer is to help with this task, so the rest of the bot logic can remain the main focus.
This section explains three ways to build your bot, depending on how its different parts should be executed.
Sequential tasks
The simplest configuration is when the bot executes its tasks sequentially, one after the other. This is the case of the bot example with the game Otteretto.
In your bot, you just need to instantiate a Recognizer when it is needed.
1from guirecognizer import Recognizer
2
3recognizer = Recognizer('config.json')
4recognizer.execute('action')
Asynchronous tasks
When your bot has to handle multiple independent tasks, an important distinction is whether those tasks need true parallelism or can make progress independently while waiting on I/O or timers. With asynchronous execution, tasks do not run simultaneously, but they can interleave their execution by yielding control when they are idle.
As an analogy, true parallelism is like having a team of employees each working at the same time in a restaurant, while asynchronous execution is like a single employee efficiently switching between tasks whenever one is waiting, such as during cooking or customer delays.
In this section, we show a small bot handling independent tasks using asynchronous execution. This execution model is used in the bot example with the game SushiGoRound. If your bot tasks truly need to run in parallel (for example, to bypass the GIL), read the next section.
Let’s have two independent tasks: one to take orders and one to make orders. We are going to use the Python library asyncio. With asyncio, tasks run cooperatively in a single thread.
1import asyncio
2import random
3from guirecognizer import Recognizer
4
5recognizer = Recognizer('config.json')
6orders = []
7
8async def takeOrders():
9 while True:
10 await asyncio.sleep(0.5 + random.random() * 5) # Simulate waiting for order.
11 order = random.randint(1, 1000)
12 recognizer.execute('order')
13 await asyncio.sleep(1) # Simulate taking one order.
14 print('Took order', order)
15 orders.append(order)
16
17async def makeOrders():
18 while True:
19 if len(orders) > 0:
20 order = orders.pop()
21 recognizer.execute('make')
22 await asyncio.sleep(5) # Simulate making one order.
23 print('Made order', order)
24 await asyncio.sleep(0.5)
25
26async def main():
27 await asyncio.gather(takeOrders(), makeOrders())
28
29asyncio.run(main())
In the console:
Took order 516
Made order 516
Took order 479
Took order 24
Made order 479
Made order 24
Took order 445
Took order 485
Made order 445
Took order 795
No explicit synchronization is needed for shared Python objects in this example. In a functional bot, lines 5, 12 and 21 should be replaced with your own usage of guirecognizer. To test this example as-is, comment out those lines.
Parallel tasks
When your bot needs true parallelism, each task must run in a separate process. The bot example with the game Cookie Clicker follows this approach..
As before, we define two tasks: one to take orders and one to make orders. We are going to use the Python library multiprocessing. This avoids the limitations imposed by Python’s Global Interpreter Lock (GIL).
1import multiprocessing as mp
2import random
3import time
4from multiprocessing.synchronize import Lock
5from guirecognizer import Recognizer
6
7def takeOrders(orders: mp.Queue, mouseLock: Lock) -> None:
8 recognizer = Recognizer('config.json')
9 while True:
10 time.sleep(0.5 + random.random() * 5) # Simulate waiting for order.
11 order = random.randint(1, 1000)
12 mouseLock.acquire()
13 recognizer.execute('order')
14 mouseLock.release()
15 time.sleep(1) # Simulate taking one order.
16 print('Took order', order)
17 orders.put(order)
18
19def makeOrders(orders: mp.Queue, mouseLock: Lock) -> None:
20 recognizer = Recognizer('config.json')
21 while True:
22 try:
23 order = orders.get(timeout=1)
24 mouseLock.acquire()
25 recognizer.execute('make')
26 mouseLock.release()
27 time.sleep(5) # Simulate making one order.
28 print('Made order', order)
29 except mp.queues.Empty:
30 pass
31 time.sleep(0.5)
32
33def main() -> None:
34 processes = []
35 orders = mp.Queue()
36 mouseLock = mp.Lock()
37 processes.append(mp.Process(target=takeOrders, args=(orders, mouseLock)))
38 processes.append(mp.Process(target=makeOrders, args=(orders, mouseLock)))
39 for process in processes:
40 process.start()
41 for process in processes:
42 process.join()
43
44if __name__ == '__main__':
45 main()
In the console:
Took order 365
Took order 227
Made order 365
Took order 704
Took order 355
Made order 227
Took order 512
Made order 704
A special structure is needed to share state between the processes. Here multiprocessing.Queue shares the orders between the two tasks.
If your bot uses the mouse, it is important to avoid simultaneous mouse actions, for example by using a lock. This is shown in the code above on lines 12, 14, 24, 26 and 36.
Instead of trying to share an instance of the Recognizer, instantiate one per process as shown on lines 8 and 20. In a functional bot, lines 8, 13, 20 and 25 should be replaced with your own usage of guirecognizer. To test this example as-is, comment out those lines.
Choosing between these approaches depends on whether your bot can run cooperatively in a single thread or requires true parallel execution.