/** Handle packets, updating game state as appropriate */ static bool _handlePackets(NinjaState *state, NinjaPacket *packet) { //Get a packet if (ninjaPacketAvailable) { //If we received a move packet, do something with it and ACK if (lastNinjaPacket.type == TYPE_MOVE) { //Only accept moves for this round if (lastNinjaPacket.round == state->round) { state->p2Move = lastNinjaPacket.data; //ACK the move packet->type = TYPE_ACK; packet->data = TYPE_MOVE; //ACK the move //Try to send the packet 5 times for (uint8_t i = 0; i < 5; i++) { //Delay a bit deepSleep(random(30) + 40); if (ANXRFSend(packet, NINJA_PACKET_SIZE)) break; } } } //ACK any latent INIT packets (challenger never got our previous ACKs) if (lastNinjaPacket.type == TYPE_INIT && state->state == STATE_SELECT_MOVE && lastNinjaPacket.src == state->p2nodeid) { packet->type = TYPE_ACK; packet->dest = state->p2nodeid; packet->src = getNodeID(); packet->data = TYPE_INIT; //Try to send the packet 5 times for (uint8_t i = 0; i < 5; i++) { //Delay a bit deepSleep(random(30) + 40); if (ANXRFSend(packet, NINJA_PACKET_SIZE)) break; } } //Handle if it's an ACK if (lastNinjaPacket.type == TYPE_ACK) { state->lastACK = lastNinjaPacket.data; } //Handle if it's a NACK if (lastNinjaPacket.type == TYPE_NACK) { state->lastNACK = lastNinjaPacket.data; } //Reset packet buffer ninjaPacketAvailable = false; } }
/** Special entry point if the player is challeged */ void ninjaChallenged(NinjaPacket packet) { //Ignore RF-related popups while they play disablePopups(); bool accept = false; PeerNode peer; NinjaState state; //Find the peer for (uint8_t i = 0; i < PEER_NODES_MAX; i++) { if (peers[i].lastSeen > 0) { if (peers[i].nodeid == packet.src) { peer = peers[i]; char b[50]; sprintf(b, "Accept Ninja\nChallenge from\n%s?", packet.src); accept = yesNoDialog(b); } } } //If the peer is known and the user accepts, ACK if (accept) { packet.type = TYPE_ACK; state.p2nodeid = peer.nodeid; state.p2Level = peer.level; state.p2Name = peer.name; state.lastACK = -1; state.lastNACK = -1; //Unwrap the tiebreaker if ((packet.data & B00000100) > 0) state.tiebreaker[0] = false; else state.tiebreaker[0] = true; if ((packet.data & B00000010) > 0) state.tiebreaker[1] = false; else state.tiebreaker[1] = true; if ((packet.data & B00000001) > 0) state.tiebreaker[2] = false; else state.tiebreaker[2] = true; } else { packet.type = TYPE_NACK; } //Repurpose the packet to send it back packet.dest = packet.src; packet.src = getNodeID(); packet.data = TYPE_INIT; //Send the NACK or ACK three times for (uint8_t i = 0; i < 5; i + 0) { if (ANXRFAvailable()) { ANXRFSend(&packet, NINJA_PACKET_SIZE); i++; } deepSleep(random(100) + 100); tick(); } //Play the game if they ack if (accept) { _doNinja(&state, &packet); } enablePopups(); }
/** GUI to get the player moves */ static uint8_t _getPlayerMove(NinjaState * state, NinjaPacket * packet) { uint32_t endTime = rtMillis() + 20000; while (1) { display.clearDisplay(); drawBitmapFlash(state->punch, 8, 0); drawBitmapFlash(state->kick, 48, 0); drawBitmapFlash(state->shield, 88, 0); drawBitmapFlash(state->right, 8, 32); drawBitmapFlash(state->down, 48, 32); drawBitmapFlash(state->up, 88, 32); //Time left display.setCursor(0, 0); display.print((endTime - rtMillis()) / 1000); safeDisplay(); uint8_t button = getButtonState(); if ((button & BUTTON_RIGHT) > 0) { clearButtonState(); return MOVE_PUNCH; } else if ((button & BUTTON_DOWN) > 0) { clearButtonState(); return MOVE_KICK; } else if ((button & BUTTON_UP) > 0) { clearButtonState(); return MOVE_SHIELD; } #ifdef MASTER else if ((button & BUTTON_ENTER) > 0) { clearButtonState(); return MOVE_TROLL; } #endif deepSleep(200); tick(); _handlePackets(state, packet); //If they're too slow, don't move if (endTime < rtMillis()) { return MOVE_NONE; } } }
void main(void) { byte duty=0; initSquareWear(); setModePWM(pinC7); // open button interrupt before going to sleep openOnBoardButtonInterrupt(callback); while(1) { // fade LED (pinC7) for(duty=0;duty<32;duty++) { setValue(pinC7, duty); delayMilliseconds(50); } for(duty=32;duty!=0;duty--) { setValue(pinC7, duty-1); delayMilliseconds(50); } // enter deep sleep: CPU clock will stop running // the only way to wake up CPU is through external, // pin-change, watchdog interrupts, or reset. deepSleep(); } }
/** Animate and perform the fight routine */ static void _fight(NinjaState *state, NinjaPacket *packet) { uint8_t result = results[state->p1Move][state->p2Move]; bool up = true; char username[USERNAME_MAX_LENGTH]; ANXGetUsername(username); //Animate the players getting ready to fight for (uint8_t i = 0; i < 6; i++) { display.clearDisplay(); display.setCursor(0, 0); display.print(state->p1Score); display.setCursor(120, 0); display.print(state->p2Score); display.setCursor(40, 28); display.print("Round "); display.print(state->round); display.drawFastHLine(0, display.height() - 10, display.width(), WHITE); //Draw the usernames display.setCursor(0, 56); display.print(username); printAlignRight(state->p2Name, 56); //Animate the players idling if (up) { drawBitmapFlash(state->p1Idle1, PLAYER_1_X, PLAYER_1_Y); drawBitmapFlash(state->p2Idle1, PLAYER_2_X, PLAYER_2_Y); } else { drawBitmapFlash(state->p1Idle2, PLAYER_1_X, PLAYER_1_Y); drawBitmapFlash(state->p2Idle2, PLAYER_2_X, PLAYER_2_Y); } up = !up; safeDisplay(); for (uint8_t i = 0; i < 15; i++) { deepSleep(25); tick(); _handlePackets(state, packet); } } for (uint8_t i = 0; i < 6; i++) { display.clearDisplay(); display.setCursor(0, 0); display.print(state->p1Score); display.setCursor(120, 0); display.print(state->p2Score); display.drawFastHLine(0, display.height() - 10, display.width(), WHITE); //Draw the names display.setCursor(0, 56); display.print(username); printAlignRight(state->p2Name, 56); //Setup the animation graphics if (up) { if (state->p1Move == MOVE_PUNCH) drawBitmapFlash(state->p1Punch1, 37, PLAYER_1_Y); if (state->p1Move == MOVE_KICK) drawBitmapFlash(state->p1Kick1, 37, PLAYER_1_Y); if (state->p1Move == MOVE_NONE) drawBitmapFlash(state->p1Idle1, 37, PLAYER_1_Y); if (state->p1Move == MOVE_SHIELD) drawBitmapFlash(state->p1Shield1, 37, PLAYER_1_Y); if (state->p2Move == MOVE_PUNCH) drawBitmapFlash(state->p2Punch2, 64, PLAYER_2_Y); if (state->p2Move == MOVE_KICK) drawBitmapFlash(state->p2Kick2, 64, PLAYER_2_Y); if (state->p2Move == MOVE_NONE) drawBitmapFlash(state->p2Idle2, 64, PLAYER_2_Y); if (state->p2Move == MOVE_SHIELD) drawBitmapFlash(state->p2Shield2, 64, PLAYER_2_Y); } else { if (state->p1Move == MOVE_PUNCH) drawBitmapFlash(state->p1Punch2, 37, PLAYER_1_Y); if (state->p1Move == MOVE_KICK) drawBitmapFlash(state->p1Kick2, 37, PLAYER_1_Y); if (state->p1Move == MOVE_NONE) drawBitmapFlash(state->p1Idle2, 37, PLAYER_1_Y); if (state->p1Move == MOVE_SHIELD) drawBitmapFlash(state->p1Shield2, 37, PLAYER_2_Y); if (state->p2Move == MOVE_PUNCH) drawBitmapFlash(state->p2Punch1, 64, PLAYER_2_Y); if (state->p2Move == MOVE_KICK) drawBitmapFlash(state->p2Kick1, 64, PLAYER_2_Y); if (state->p2Move == MOVE_NONE) drawBitmapFlash(state->p2Idle1, 64, PLAYER_2_Y); if (state->p2Move == MOVE_SHIELD) drawBitmapFlash(state->p2Shield1, 64, PLAYER_2_Y); } if (state->p1Move == MOVE_TROLL) drawBitmapFlash(state->troll, 23, PLAYER_1_Y); if (state->p2Move == MOVE_TROLL) drawBitmapFlash(state->troll, 64, PLAYER_2_Y); up = !up; safeDisplay(); for (uint8_t i = 0; i < 5; i++) { deepSleep(100); tick(); } } display.clearDisplay(); display.drawFastHLine(0, display.height() - 10, display.width(), WHITE); //Draw the names display.setCursor(0, 56); display.print(username); printAlignRight(state->p2Name, 56); //Check the tiebreaker if (result == RESULT_TIE) { if (state->tiebreaker[state->round - 1]) { result = RESULT_WIN; } else { result = RESULT_LOSE; } } switch (result) { case RESULT_WIN: display.setCursor(67, 28); display.print("You Win!"); state->p1Score++; drawBitmapFlash(state->p1Idle1, 37, PLAYER_1_Y); drawBitmapFlash(state->p2Dead, 64, PLAYER_2_Y); break; case RESULT_LOSE: display.setCursor(4, 28); display.print("You Lose :("); state->p2Score++; drawBitmapFlash(state->p1Dead, 37, PLAYER_1_Y); drawBitmapFlash(state->p2Idle1, 64, PLAYER_2_Y); break; } display.setCursor(0, 0); display.print(state->p1Score); display.setCursor(120, 0); display.print(state->p2Score); safeDisplay(); //3 second delay for (uint8_t i = 0; i < 15; i++) { deepSleep(200); tick(); } }
/** Challenge another player at ninja */ void ninja() { //Used to store game state NinjaState state; state.lastACK = -1; state.lastNACK = -1; //Used as a timer to timeout of things uint32_t endTime; //Reset game packet state ninjaPacketAvailable = false; //Select a peer int16_t nodeid = getPeerFromUser(); if (nodeid == -1) { return; } //Up until this point we can interrupt the user disablePopups(); //Establish game state PeerNode peer = peers[nodeid]; state.p2nodeid = peer.nodeid; state.p2Level = peer.level; state.p2Name = peer.name; //Pre-determine tiebreakers by round based on selected player int16_t wthreshold = (100 * (ANXGetLevel() - state.p2Level + 3)) / 6; for (uint8_t i = 0; i < 3; i++) { state.tiebreaker[i] = random(100) <= wthreshold; } //Construct most of the packet now, we'll re-use it later NinjaPacket packet; packet.port = PORT_NINJA; packet.src = getNodeID(); packet.dest = peer.nodeid; packet.gameid = random(256); //Setup game with another player packet.type = TYPE_INIT; packet.data = state.tiebreaker[0] << 2 | state.tiebreaker[1] << 1 | state.tiebreaker[2]; //Wait for the other player to accept endTime = rtMillis() + 20000; char challenge[40]; memset(challenge, '\0', 40); sprintf(challenge, "Challenging\n%s\nLevel %d...", peer.name, peer.level); statusDialog(challenge); safeDisplay(); //Wait for ack while (state.lastACK != TYPE_INIT) { ANXRFSend(&packet, NINJA_PACKET_SIZE); //Roughly wait 200ms until next send for (uint8_t i = 0; i < 8; i++) { tick(); _handlePackets(&state, &packet); deepSleep(random(80)); } //Quit if time expires if (rtMillis() > endTime) { statusDialog("Challenge\nTimed out."); safeWaitForButton(); enablePopups(); return; } //Quit if NACKed by other user if ( state.lastNACK == TYPE_INIT) { statusDialog("Challenge\nRejected :("); safeWaitForButton(); enablePopups(); return; } } //Play the game _doNinja(&state, &packet); }
/** Primary state machine for ninja fight game handles exchange of packets and setting up game between the two players */ static void _doNinja(NinjaState *state, NinjaPacket *packet) { uint32_t endTime = 0; //Init the game state state->p1Idle1 = getBitmapMetadata(NINJA_P1_IDLE1_address); state->p2Idle1 = getBitmapMetadata(NINJA_P2_IDLE1_address); state->p1Idle2 = getBitmapMetadata(NINJA_P1_IDLE2_address); state->p2Idle2 = getBitmapMetadata(NINJA_P2_IDLE2_address); state->p1Punch1 = getBitmapMetadata(NINJA_P1_PUNCH1_address); state->p2Punch1 = getBitmapMetadata(NINJA_P2_PUNCH1_address); state->p1Punch2 = getBitmapMetadata(NINJA_P1_PUNCH2_address); state->p2Punch2 = getBitmapMetadata(NINJA_P2_PUNCH2_address); state->p1Kick1 = getBitmapMetadata(NINJA_P1_KICK1_address); state->p2Kick1 = getBitmapMetadata(NINJA_P2_KICK1_address); state->p1Kick2 = getBitmapMetadata(NINJA_P1_KICK2_address); state->p2Kick2 = getBitmapMetadata(NINJA_P2_KICK2_address); state->p1Shield1 = getBitmapMetadata(NINJA_P1_SHIELD1_address); state->p2Shield1 = getBitmapMetadata(NINJA_P2_SHIELD1_address); state->p1Shield2 = getBitmapMetadata(NINJA_P1_SHIELD2_address); state->p2Shield2 = getBitmapMetadata(NINJA_P2_SHIELD2_address); state->p1Dead = getBitmapMetadata(NINJA_P1_DEAD_address); state->p2Dead = getBitmapMetadata(NINJA_P2_DEAD_address); state->troll = getBitmapMetadata(NINJA_DT_address); state->punch = getBitmapMetadata(NINJA_PUNCH_address); state->kick = getBitmapMetadata(NINJA_KICK_address); state->shield = getBitmapMetadata(NINJA_SHIELD_address); state->up = getBitmapMetadata(NINJA_UP_address); state->down = getBitmapMetadata(NINJA_DOWN_address); state->right = getBitmapMetadata(NINJA_RIGHT_address); state->state = STATE_SELECT_MOVE; state->round = 1; state->p1Score = 0; state->p2Score = 0; while (state->p1Score < 2 && state->p2Score < 2) { if (state->state == STATE_SELECT_MOVE) { state->lastACK = -1; //Set p2Move to -1 *before* _getPlayerMove (which could change it!) state->p2Move = -1; //other player move state->p1Move = _getPlayerMove(state, packet); endTime = rtMillis() + 20000; bool ackRecv = false; bool moveRecv = false; //Wait for a bit sending and receiving until we're synced with other player while (state->lastACK != TYPE_MOVE || state->p2Move < 0) { //Show a dialog to the player char wait[32]; sprintf(wait, "Waiting for\nplayer %d", (endTime - rtMillis()) / 1000); statusDialog(wait); safeDisplay(); //Keep sending until an ACK is received if (state->lastACK != TYPE_MOVE) { //Fill out the rest of the packet packet->type = TYPE_MOVE; packet->data = state->p1Move; packet->round = state->round; ANXRFSend(packet, NINJA_PACKET_SIZE); } //Delay and do some ticking (400ms) for (uint8_t i = 0; i < 16; i++) { deepSleep(25); tick(); //Process any data that comes in _handlePackets(state, packet); } //give up on other player if (rtMillis() > endTime) { state->state = STATE_ABORT; break; } } //Bump them over to fight if (state->state != STATE_ABORT) state->state = STATE_FIGHT; } else if (state->state == STATE_FIGHT) { _fight(state, packet); state->state = STATE_SELECT_MOVE; state->round++; } else if (state->state == STATE_ABORT) { break; } deepSleep(200); tick(); } _gameOver(state); enablePopups(); }
/** Interactively accepts input from the user Max input size is determined by remaining cells at given start row. No scrolling. */ void ANXInput(char *message, uint8_t startX, uint8_t startY, uint8_t maxChars, uint8_t maxCharsPerRow) { int messageLen = strlen(message); //Size of the message int cursor = messageLen; //Location of cursor in the text, put cursor at end of message to continue uint8_t row = 0; //Row of the cursor uint8_t col = 0; //Col of the cursor uint8_t cindex = 0; //index within input char array int maxCols = (display.width() - startX) / ANX_FONT_WIDTH; //Max number of cols available int maxRows = (display.height() - startY) / ANX_FONT_HEIGHT; //Max number of rows available int maxLen = min(maxCols * maxRows, maxChars); //Maximum possible size of the message (limited by input space and buffer) //Use the user specified chars per row if specified if (maxCharsPerRow > 0) { maxCols = min(maxCharsPerRow, maxCols); } //Print the message, if any ANXInputMoveCursor(row, col, startX, startY); display.print(message); //Iteractively get input from user while (1) { row = cursor / maxCols; col = cursor % maxCols; ANXInputClearChar(row, col, startX, startY); ANXInputDrawCursor(row, col, startX, startY); if (message[cursor] != NULL) { ANXInputMoveCursor(row, col, startX, startY); display.print(message[cursor]); } safeDisplay(); uint8_t button = waitForButton(); if ((button & BUTTON_DOWN) > 0) { //Move char index to the end pro-actively since unsigned won't wrap to -1 if (cindex == 0) cindex = INPUT_CHARS_COUNT; cindex--; if (cursor >= messageLen) { messageLen++; } message[cursor] = INPUT_CHARS[cindex]; //Make sure we're not null terminating the string early if (cursor > 0) { if (message[cursor - 1] == '\0') message[cursor - 1] = ' '; } deepSleep(BUTTON_REPEAT_DELAY); } if ((button & BUTTON_UP) > 0) { cindex++; if (cindex >= INPUT_CHARS_COUNT) cindex = 0; if (cursor >= messageLen) { messageLen++; } message[cursor] = INPUT_CHARS[cindex]; //Make sure we're not null terminating the string early if (cursor > 0) { if (message[cursor - 1] == '\0') message[cursor - 1] = ' '; } deepSleep(BUTTON_REPEAT_DELAY); } if ((button & BUTTON_RIGHT) > 0) { ANXInputDrawCursor(row, col, startX, startY); //Prevent cursor from going off the deep end if (cursor < maxLen - 1) { cursor++; } if (cursor < messageLen) { cindex = ANXIndexOf(INPUT_CHARS, message[cursor]); } else { messageLen++; cindex = 0; } //Make sure we're not null terminating the string early if (cursor > 0) { if (message[cursor - 1] == '\0') message[cursor - 1] = ' '; } //Don't allow repeats clearButtonState(); } if ((button & BUTTON_LEFT) > 0) { //Prevent cursor underun if (cursor > 0) { ANXInputDrawCursor(row, col, startX, startY); cursor--; } if (cursor < messageLen) { cindex = ANXIndexOf(INPUT_CHARS, message[cursor]); } else { messageLen++; cindex = 0; } //Don't allow repeats clearButtonState(); } if ((button & BUTTON_ENTER) > 0) { //Don't allow repeats clearButtonState(); return; } } }