Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# hackerpet games repository

This is a compilation of games for [hackerpet][hackerpet_repo]-modified
CleverPet Hubs. You'll find here games to try out with your dog or cat.
Feel free to suggest modifications to games or even add new games here that
CleverPet Hubs. You'll find here games to try out with your dog or cat.
Feel free to suggest modifications to games or even add new games here that
you've made yourself!

### Games to Teach your Player (dog, cat, pig, ...) to Use CleverPet
CleverPet developed this sequence of games to teach players to use the CleverPet Hub's
CleverPet developed this sequence of games to teach players to use the CleverPet Hub's
touch interface, and are best played in the order below. When the player becomes proficient
at the current challenge, switch them to the next one!

Expand All @@ -27,8 +27,8 @@ you've made yourself!


### Community Contributed Games
Anyone can make a game for the Hub! If you'd like to add yours below, just create a pull
request to this repository. We'll commit all appropriate games.
Anyone can make a game for the Hub! If you'd like to add yours below, just create a pull
request to this repository. We'll commit all appropriate games.

|||    |||
|:---|:---|:-:|:---|---|
Expand All @@ -38,15 +38,16 @@ you've made yourself!
| 15. | [**WhackAMole**](./games/015_WhackAMole) <br><sup>CleverPet&nbsp;<info@clever.pet></sup>|[![Install WhackAMole](./docs/images/install.png)][WhackAMole]| This is a more "active" game for the Hub that challenges your player's "paw-eye" coordination. | ![]() |
| 16. | [**ControlPet**](./games/016_ControlPet) <br><sup>Michael Gschwandtner&nbsp;<mgschwan -at- gmail.com></sup> | [![Install ControlPet](./docs/images/install.png)][ControlPet] | Remote control app for the hub. Control the hub either via a webinterface or from a python program. Enables rapid prototyping of new ideas and guided learning of the hub | ![]() |
| 17. | [**Symon**](./games/017_Symon) <br><sup>Jelmer&nbsp;Tiete&nbsp;<jelmer@tiete.be></sup> | [![Install Symon](./docs/images/install.png)][Symon] | CleverPet's take on the classic Simon game! | ![]() |
| 18. | [**OddOneOutRepeated**](./games/018_OddOneOutRepeated) <br><sup>CleverPet&nbsp;<info@clever.pet></sup> | This game is for testing your pet's ability distinguishing colors. | ![]() |

--------

## How to install games

Once you've modified your Hub to work with hackerpet, the easiest way is to install a game is to just click the install icon (![install icon](./docs/images/install.png))! You'll then sign in to Particle and can choose which Photon you'd like to put the game on to.
Once you've modified your Hub to work with hackerpet, the easiest way is to install a game is to just click the install icon (![install icon](./docs/images/install.png))! You'll then sign in to Particle and can choose which Photon you'd like to put the game on to.

Alternatively, you can clone or download this repository, and then install the game to your Hub using one of:
1. [particle-cli][particle_cli],
1. [particle-cli][particle_cli],
2. [Particle IDE (DEV)][particle_dev] (slightly easier to use), or
3. [Particle Workbench][particle_vsc] (more sophisticated) for Visual Studio Code

Expand All @@ -55,29 +56,29 @@ Alternatively, you can clone or download this repository, and then install the g
You can add your own game to this list by simply submitting a pull request (PR) in
this repository.
A few guidelines for assuring a quick PR approval:
* Please make sure you've tested the game and that you've succeeded at training a player to play it!
* Please make sure you've tested the game and that you've succeeded at training a player to play it!
* Please make sure that the game/app isn't a duplicate of others already here
* In your PR, make sure you update both this file and [games.json][games.json]
* Please submit one game per PR :)
* Ensure you've already completed the [CleverPet Individual
Contributor License Agreement (CLA)][CLA], which is based on the Google CLA.
Contributor License Agreement (CLA)][CLA], which is based on the Google CLA.

If you want to also include your source in this repository, you can make a new folder
If you want to also include your source in this repository, you can make a new folder
under the [games folder][games_folder] named after your game. The directory structure of
your game should follow the standard Particle project structure. You can see an
example of the structure in the [`hello-world`][hello_world] game folder.

## What's the CleverPet Hub? What's hackerpet?
## What's the CleverPet Hub? What's hackerpet?

The [CleverPet Hub][cleverpet.io] is a programmable device that lets you
automatically train and interact with other species. It's kind of like a "game
console for dogs and cats".

<p align="center"> <img width="460" src="docs/images/hub1.png"> </p>

The Hub is built on the [Particle][particle.io] platform, and with **hackerpet**
we've open-sourced CleverPet's player-training software, which means that the full
suite of tools Particle has built can be used to control the CleverPet Hub.
The Hub is built on the [Particle][particle.io] platform, and with **hackerpet**
we've open-sourced CleverPet's player-training software, which means that the full
suite of tools Particle has built can be used to control the CleverPet Hub.

You can find the main Particle library to control the CleverPet Hub in the
[hackerpet repository][hackerpet_repo] or on the
Expand Down
2 changes: 2 additions & 0 deletions games/018_OddOneOutRepeated/project.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
name=EatingTheFood
dependencies.hackerpet=0.2.2
249 changes: 249 additions & 0 deletions games/018_OddOneOutRepeated/src/OddOneOutRepeated.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
// This #include statement was automatically added by the Particle IDE.
#include <hackerpet.h>
#include <functional>

HubInterface hub;

const unsigned long FOODTREAT_DELAY = 600; // (ms) delay for reward sound
const unsigned long TOUCHPAD_DELAY = 300; // (ms) delay for touchpad sound
const unsigned long FOODTREAT_DURATION = 6000; // (ms) how long to present foodtreat
const unsigned long SOUND_DELAY = 600;
const unsigned int DIM = 30;
const unsigned long LIGHT_REFRESH = 20000;

const unsigned int LIGHTS[3] = { hub.LIGHT_LEFT, hub.LIGHT_MIDDLE, hub.LIGHT_RIGHT };

struct Game {
bool wait_after_game; // this is whether the hub should return to the waiting state between games
unsigned int timeout_ms; // thie is how long without interaction the hub will wait before returning to the waiting state
unsigned int loss_timer_ms; // this is how long after a pet's loss that the hub waits before starting a new game or returns to the waiting state
unsigned char colors[8][2]; // this is an array of BY values for the colors that will be used for the game
unsigned int rounds_per_game; // this is the number of times the pet needs to win before being presented a foodtreat
unsigned int tries_per_game; // this is the number of times the pet is allowed to lose before restarting the game
// the functions below are executed upon certain events being triggered
// unsigned char* arguments are arrays to be modified in-place with the index of the color that the lights should be
// for example: colors = [[0, 0], [255, 255]] and modifying this argument to be [0, 1, 0] will cause the lights to be off, white, and off, respectively
// the int argument is the index of the button that was pressed by the pet
std::function<void(unsigned char*)> on_round_start; // initialize the game
std::function<void(unsigned char*)> periodic_update; // updates that happen each time this game loop runs
std::function<void(unsigned char*, int)> on_touch; // updates that happen whenever a pet presses a button
std::function<bool(int)> did_pet_win; // condition check for if the pet won the round
std::function<void()> on_pet_win; // updates that happen whenever a pet wins a round
std::function<bool(int)> did_pet_lose; // condition check for if the pet lost the round
std::function<void()> on_pet_lose; // updates that happen whenever a pet loses a round
};

// enables simultaneous execution of application and system thread
SYSTEM_THREAD(ENABLED);

void play(Game g) {
yield_begin();
yield_wait_for((hub.IsReady() && hub.FoodmachineState() == hub.FOODMACHINE_IDLE && not hub.AnyButtonPressed()),);

static bool waiting = true;
static bool restart;
static int rounds;
static int press;
static int foodtreat_state;
static int turn_timestamp;
static unsigned char colors[3] = {};
static unsigned char temp_colors[3] = {};
static unsigned long refresh = millis();

// waiting
hub.SetLights(hub.LIGHT_BTNS, DIM, DIM, 0);
for (;;) {
// periodically refresh lights
if (millis() > refresh + LIGHT_REFRESH) {
hub.SetLights(hub.LIGHT_BTNS, DIM, DIM, 0);
refresh = millis();
}

// wait until any valid button press
yield();
press = hub.AnyButtonPressed();
if (press == hub.BUTTON_LEFT || press == hub.BUTTON_MIDDLE || press == hub.BUTTON_RIGHT) {
yield_sleep_ms(TOUCHPAD_DELAY,);
yield_wait_for((!hub.AnyButtonPressed()),);
break;
}
}

// reinitializing
restart = true;
rounds = 0;
turn_timestamp = millis();
for (;;) {
if (restart) {
rounds++;

// reinitialize game for each round
press = 0;
foodtreat_state = 0;
colors[0] = 0;
colors[1] = 0;
colors[2] = 0;
hub.SetButtonAudioEnabled(true);
hub.SetDIResetLock(true);
g.on_round_start(colors);

hub.SetLights(hub.LIGHT_LEFT, g.colors[colors[0]][0], g.colors[colors[0]][1], 0);
hub.SetLights(hub.LIGHT_MIDDLE, g.colors[colors[1]][0], g.colors[colors[1]][1], 0);
hub.SetLights(hub.LIGHT_RIGHT, g.colors[colors[2]][0], g.colors[colors[2]][1], 0);

restart = false;
}

// game logic updating and optimize light updates
for (int i = 0; i < 3; i++) {
temp_colors[i] = colors[i];
}
g.periodic_update(colors);
for (int i = 0; i < 3; i++) {
if (temp_colors[i] != colors[i]) {
hub.SetLights(LIGHTS[i], g.colors[colors[i]][0], g.colors[colors[i]][1], 0);
}
}

// periodically refresh lights
if (millis() > refresh + LIGHT_REFRESH) {
hub.SetLights(hub.LIGHT_LEFT, g.colors[colors[0]][0], g.colors[colors[0]][1], 0);
hub.SetLights(hub.LIGHT_MIDDLE, g.colors[colors[1]][0], g.colors[colors[1]][1], 0);
hub.SetLights(hub.LIGHT_RIGHT, g.colors[colors[2]][0], g.colors[colors[2]][1], 0);
refresh = millis();
}

// handle only valid button presses
yield();
press = hub.AnyButtonPressed();
if (press == hub.BUTTON_LEFT || press == hub.BUTTON_MIDDLE || press == hub.BUTTON_RIGHT) {
// convert press from bit flag to 0-index
if (press == hub.BUTTON_LEFT) {
press = 0;
}
else if (press == hub.BUTTON_MIDDLE) {
press = 1;
}
else if (press == hub.BUTTON_RIGHT) {
press = 2;
}
yield_sleep_ms(TOUCHPAD_DELAY,);
yield_wait_for((!hub.AnyButtonPressed()),);

turn_timestamp = millis();
// game logic updating and optimize light updates
for (int i = 0; i < 3; i++) {
temp_colors[i] = colors[i];
}
g.on_touch(colors, press);
if (g.did_pet_win(press)) {
g.on_pet_win();
hub.PlayAudio(hub.AUDIO_POSITIVE, 80);
if (rounds == g.rounds_per_game) {
// shut off lights and emit sound to signal game end
hub.SetLights(hub.LIGHT_BTNS, 0, 0, 0);
yield_sleep_ms(FOODTREAT_DELAY,);
// dispense treat
do {
foodtreat_state = hub.PresentAndCheckFoodtreat(FOODTREAT_DURATION);
yield();
}
while (foodtreat_state != hub.PACT_RESPONSE_FOODTREAT_NOT_TAKEN && foodtreat_state != hub.PACT_RESPONSE_FOODTREAT_TAKEN);
if (g.wait_after_game) {
break;
}
rounds = 0;
}
restart = true;
}
else if (g.did_pet_lose(press)) {
g.on_pet_lose();
hub.PlayAudio(hub.AUDIO_NEGATIVE, 80);
if (rounds == g.tries_per_game) {
// shut off lights and emit sound to signal game end
hub.SetLights(hub.LIGHT_BTNS, 0, 0, 0);
yield_sleep_ms(g.loss_timer_ms,);
if (g.wait_after_game) {
break;
}
rounds = 0;
}
restart = true;
}
else {
// update lights and emit sound
hub.PlayAudio(hub.AUDIO_DO, 60);
for (int i = 0; i < 3; i++) {
if (temp_colors[i] != colors[i]) {
hub.SetLights(LIGHTS[i], g.colors[colors[i]][0], g.colors[colors[i]][1], 0);
}
}
yield_sleep_ms(TOUCHPAD_DELAY,);
}
}
else if (millis() > turn_timestamp + g.timeout_ms) {
waiting = true;
break;
}
}

hub.SetButtonAudioEnabled(false);
hub.SetDIResetLock(false);

yield_finish();
}

//
// Game configuration is below this
//

void setup() {
hub.Initialize(__FILE__);
}

int color_a;
int color_b;
int answer;
int tries;

void loop() {
hub.Run(20);

play({
.wait_after_game = true,
.timeout_ms = 30000,
.loss_timer_ms = 10000,
.colors = {
{90, 00},
{00, 90},
{70, 70},
},
.rounds_per_game = 3,
.tries_per_game = 2,
.on_round_start = [](unsigned char* colors) {
do {
color_a = random(0, 3);
color_b = random(0, 3);
}
while (color_a == color_b);
answer = random(0, 3);
colors[0] = color_a;
colors[1] = color_a;
colors[2] = color_a;
colors[answer] = color_b;
tries = 0;
},
.periodic_update = [](unsigned char* colors) {},
.on_touch = [](unsigned char* colors, int pad) {
tries++;
},
.did_pet_win = [](int pad) {
return pad == answer;
},
.on_pet_win = []() {},
.did_pet_lose = [](int pad) {
return tries >= 3;
},
.on_pet_lose = []() {},
});
}