اجرای بازی مار (Snake) نوکیا با آردوینو [سه گام با عکس]

فهرست مطالب

مقدمه

همگی بازی خاطره‌انگیز Snake (مار) را در گوشی‌های قدیمی نوکیا به یاد دارید. گاهی اوقات دلتان برای این بازی های خاطره‌انگیز تنگ می‌شود اما دسترسی به گوشی‌های نوکیا برای انجام بازی مقدور نمی‌باشد. در این صورت شما به سادگی می‌توانید توسط برد آردوینو و یک نمایشگر دلخواه، این بازی را برای خودتان ساخته و از آن لذت ببرید.
ماری که با خوردن غذا بزرگ‌تر می‌شد و بازی تا جایی ادامه پیدا می‌کرد که مار به خودش برخورد نکند. هر چه این مار بزرگ تر می‌شد، ادامه بازی و کنترل آن سخت‌تر و همچنین رکورد شما نیز بیشتر می‌شد.

بازی مار snake با آردوینو - مقدمه

آنچه در این آموزش یاد می‌گیرید

همانطور که بیان شد، در این آموزش قصد داریم توسط برد آردوینو، بازی Snake (ماربازی) گوشی نوکیا را شبیه‌سازی کنیم. برای این کار از یک جوی‌استیک و یک نمایشگر نیز کمک می‌گیریم. این کار را به دو روش و به وسیله دو نمایشگر متفاوت اجرا می‌کنیم:

  • اجرای بازی با نمایشگر ماتریس LED درایور Max7219
  • اجرای بازی توسط نمایشگر LCD کاراکتری 1602
بازی مار snake با آردوینو - نمایشگرها

لوازمی که به آن احتیاج دارید

جهت ادامه آموزش به قطعات و لوازم زیر نیاز دارید:

بازی مار snake با آردوینو - لوازم مورد نیاز

قطعات موردنیاز

برد آردوینو Uno × 1
برد بورد 400 حفره ای 5.5 * 8.5 سانتی متر × 1
ماژول صفحه نمایش LED ماتریسی 8X8 تگ رنگ دارای چیپ درایور MAX7219 × 1
شیلد نمایشگر LCD کاراکتری 1602 آردوینو × 1
ماژول جوی استیک دو محوره × 1
ولوم 10 کیلو اهم بسته 10 تایی × 1
سیم جامپر نری به نری 20 سانتی متری بسته 40 تایی × 1
سیم جامپر نری به مادگی 20 سانتی متری بسته 40 تایی × 1

نرم افزارهای موردنیاز

Arduino IDE

اجرای بازی Snake توسط نمایشگر ماتریس LED با درایور Max7219 و آردوینو

گام اول: سیم‌بندی

ابتدا توسط مدار زیر قطعات را به آردوینو متصل کنید:

Snake-game Interface Circuit

گام دوم: کد برنامه

در ادامه و پس از اتصال آردوینو به کامپیوتر، برنامه زیر را بر روی آردوینوی خود آپلود نمائید :


/*
  Made on 01 Sep 2021
  
Home
*/ #include "LedControl.h" // LedControl library is used for controlling a LED matrix. Find it using Library Manager or download zip here: https://github.com/wayoda/LedControl // there are defined all the pins struct Pin { static const short joystickX = A5; // joystick X axis pin static const short joystickY = A4; // joystick Y axis pin static const short joystickVCC = 1; // virtual VCC for the joystick (Analog 1) (to make the joystick connectable right next to the arduino nano) static const short joystickGND = 2; // virtual GND for the joystick (Analog 0) (to make the joystick connectable right next to the arduino nano) static const short potentiometer = A3; // potentiometer for snake speed control static const short CLK = 8; // clock for LED matrix static const short CS = 9; // chip-select for LED matrix static const short DIN = 10; // data-in for LED matrix }; // LED matrix brightness: between 0(darkest) and 15(brightest) const short intensity = 8; // lower = faster message scrolling const short messageSpeed = 5; // initial snake length (1...63, recommended 3) const short initialSnakeLength = 3; void setup() { Serial.begin(115200); // set the same baud rate on your Serial Monitor initialize(); // initialize pins & LED matrix calibrateJoystick(); // calibrate the joystick home (do not touch it) showSnakeMessage(); // scrolls the 'snake' message around the matrix } void loop() { generateFood(); // if there is no food, generate one scanJoystick(); // watches joystick movements & blinks with food calculateSnake(); // calculates snake parameters handleGameStates(); // uncomment this if you want the current game board to be printed to the serial (slows down the game a bit) // dumpGameBoard(); } // --------------------------------------------------------------- // // -------------------- supporting variables --------------------- // // --------------------------------------------------------------- // LedControl matrix(Pin::DIN, Pin::CLK, Pin::CS, 1); struct Point { int row = 0, col = 0; Point(int row = 0, int col = 0): row(row), col(col) {} }; struct Coordinate { int x = 0, y = 0; Coordinate(int x = 0, int y = 0): x(x), y(y) {} }; bool win = false; bool gameOver = false; // primary snake head coordinates (snake head), it will be randomly generated Point snake; // food is not anywhere yet Point food(-1, -1); // construct with default values in case the user turns off the calibration Coordinate joystickHome(500, 500); // snake parameters int snakeLength = initialSnakeLength; // chosen by the user in the config section int snakeSpeed = 1; // will be set according to potentiometer value, cannot be 0 int snakeDirection = 0; // if it is 0, the snake does not move // direction constants const short up = 1; const short right = 2; const short down = 3; // 'down - 2' must be 'up' const short left = 4; // 'left - 2' must be 'right' // threshold where movement of the joystick will be accepted const int joystickThreshold = 160; // artificial logarithmity (steepness) of the potentiometer (-1 = linear, 1 = natural, bigger = steeper (recommended 0...1)) const float logarithmity = 0.4; // snake body segments storage int gameboard[8][8] = {}; // --------------------------------------------------------------- // // -------------------------- functions -------------------------- // // --------------------------------------------------------------- // // if there is no food, generate one, also check for victory void generateFood() { if (food.row == -1 || food.col == -1) { // self-explanatory if (snakeLength >= 64) { win = true; return; // prevent the food generator from running, in this case it would run forever, because it will not be able to find a pixel without a snake } // generate food until it is in the right position do { food.col = random(8); food.row = random(8); } while (gameboard[food.row][food.col] > 0); } } // watches joystick movements & blinks with food void scanJoystick() { int previousDirection = snakeDirection; // save the last direction long timestamp = millis(); while (millis() < timestamp + snakeSpeed) { // calculate snake speed exponentially (10...1000ms) float raw = mapf(analogRead(Pin::potentiometer), 0, 1023, 0, 1); snakeSpeed = mapf(pow(raw, 3.5), 0, 1, 10, 1000); // change the speed exponentially if (snakeSpeed == 0) snakeSpeed = 1; // safety: speed can not be 0 // determine the direction of the snake analogRead(Pin::joystickY) < joystickHome.y - joystickThreshold ? snakeDirection = up : 0; analogRead(Pin::joystickY) > joystickHome.y + joystickThreshold ? snakeDirection = down : 0; analogRead(Pin::joystickX) < joystickHome.x - joystickThreshold ? snakeDirection = left : 0; analogRead(Pin::joystickX) > joystickHome.x + joystickThreshold ? snakeDirection = right : 0; // ignore directional change by 180 degrees (no effect for non-moving snake) snakeDirection + 2 == previousDirection && previousDirection != 0 ? snakeDirection = previousDirection : 0; snakeDirection - 2 == previousDirection && previousDirection != 0 ? snakeDirection = previousDirection : 0; // intelligently blink with the food matrix.setLed(0, food.row, food.col, millis() % 100 < 50 ? 1 : 0); } } // calculate snake movement data void calculateSnake() { switch (snakeDirection) { case up: snake.row--; fixEdge(); matrix.setLed(0, snake.row, snake.col, 1); break; case right: snake.col++; fixEdge(); matrix.setLed(0, snake.row, snake.col, 1); break; case down: snake.row++; fixEdge(); matrix.setLed(0, snake.row, snake.col, 1); break; case left: snake.col--; fixEdge(); matrix.setLed(0, snake.row, snake.col, 1); break; default: // if the snake is not moving, exit return; } // if there is a snake body segment, this will cause the end of the game (snake must be moving) if (gameboard[snake.row][snake.col] > 1 && snakeDirection != 0) { gameOver = true; return; } // check if the food was eaten if (snake.row == food.row && snake.col == food.col) { food.row = -1; // reset food food.col = -1; // increment snake length snakeLength++; // increment all the snake body segments for (int row = 0; row < 8; row++) { for (int col = 0; col < 8; col++) { if (gameboard[row][col] > 0 ) { gameboard[row][col]++; } } } } // add new segment at the snake head location gameboard[snake.row][snake.col] = snakeLength + 1; // will be decremented in a moment // decrement all the snake body segments, if segment is 0, turn off the corresponding led for (int row = 0; row < 8; row++) { for (int col = 0; col < 8; col++) { // if there is a body segment, decrement it's value if (gameboard[row][col] > 0 ) { gameboard[row][col]--; } // display the current pixel matrix.setLed(0, row, col, gameboard[row][col] == 0 ? 0 : 1); } } } // causes the snake to appear on the other side of the screen if it gets out of the edge void fixEdge() { snake.col < 0 ? snake.col += 8 : 0; snake.col > 7 ? snake.col -= 8 : 0; snake.row < 0 ? snake.row += 8 : 0; snake.row > 7 ? snake.row -= 8 : 0; } void handleGameStates() { if (gameOver || win) { unrollSnake(); showScoreMessage(snakeLength - initialSnakeLength); if (gameOver) showGameOverMessage(); else if (win) showWinMessage(); // re-init the game win = false; gameOver = false; snake.row = random(8); snake.col = random(8); food.row = -1; food.col = -1; snakeLength = initialSnakeLength; snakeDirection = 0; memset(gameboard, 0, sizeof(gameboard[0][0]) * 8 * 8); matrix.clearDisplay(0); } } void unrollSnake() { // switch off the food LED matrix.setLed(0, food.row, food.col, 0); delay(800); // flash the screen 5 times for (int i = 0; i < 5; i++) { // invert the screen for (int row = 0; row < 8; row++) { for (int col = 0; col < 8; col++) { matrix.setLed(0, row, col, gameboard[row][col] == 0 ? 1 : 0); } } delay(20); // invert it back for (int row = 0; row < 8; row++) { for (int col = 0; col < 8; col++) { matrix.setLed(0, row, col, gameboard[row][col] == 0 ? 0 : 1); } } delay(50); } delay(600); for (int i = 1; i <= snakeLength; i++) { for (int row = 0; row < 8; row++) { for (int col = 0; col < 8; col++) { if (gameboard[row][col] == i) { matrix.setLed(0, row, col, 0); delay(100); } } } } } // calibrate the joystick home for 10 times void calibrateJoystick() { Coordinate values; for (int i = 0; i < 10; i++) { values.x += analogRead(Pin::joystickX); values.y += analogRead(Pin::joystickY); } joystickHome.x = values.x / 10; joystickHome.y = values.y / 10; } void initialize() { pinMode(Pin::joystickVCC, OUTPUT); digitalWrite(Pin::joystickVCC, HIGH); pinMode(Pin::joystickGND, OUTPUT); digitalWrite(Pin::joystickGND, LOW); matrix.shutdown(0, false); matrix.setIntensity(0, intensity); matrix.clearDisplay(0); randomSeed(analogRead(A5)); snake.row = random(8); snake.col = random(8); } void dumpGameBoard() { String buff = "\n\n\n"; for (int row = 0; row < 8; row++) { for (int col = 0; col < 8; col++) { if (gameboard[row][col] < 10) buff += " "; if (gameboard[row][col] != 0) buff += gameboard[row][col]; else if (col == food.col && row == food.row) buff += "@"; else buff += "-"; buff += " "; } buff += "\n"; } Serial.println(buff); } // --------------------------------------------------------------- // // -------------------------- messages --------------------------- // // --------------------------------------------------------------- // const PROGMEM bool snakeMessage[8][84] = { {0,0,0,0,0,0,0,0, 0,1,1,0,0,0,0,1,1,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0, 0,1,1,1,0,0,1,1,1,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0, 0,1,1,0,1,1,0,1,1,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0, 0,1,1,0,1,1,0,1,1,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0, 0,1,1,0,1,1,0,1,1,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0, 0,1,1,0,1,1,0,1,1,0,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0, 0,1,1,0,0,0,0,1,1,0,1,1,0,0,1,0,0,0,0,1,1,0,0,0,1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0,0,0,0,0,0,0,0, 0,1,1,0,0,0,0,1,1,0,1,1,0,0,0,1,0,0,0,1,1,0,0,0,1, 0,1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0} }; const PROGMEM bool gameOverMessage[8][90] = { {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0} }; const PROGMEM bool scoreMessage[8][58] = { {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }; const PROGMEM bool digits[][8][8] = { { {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 1, 1, 1, 1, 0, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 1, 1, 0, 1, 1, 1, 0}, {0, 1, 1, 1, 0, 1, 1, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 0, 1, 1, 1, 1, 0, 0} }, { {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 1, 1, 1, 1, 1, 1, 0} }, { {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 1, 1, 1, 1, 0, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 0, 0, 0, 0, 1, 1, 0}, {0, 0, 0, 0, 1, 1, 0, 0}, {0, 0, 1, 1, 0, 0, 0, 0}, {0, 1, 1, 0, 0, 0, 0, 0}, {0, 1, 1, 1, 1, 1, 1, 0} }, { {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 1, 1, 1, 1, 0, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 0, 0, 0, 0, 1, 1, 0}, {0, 0, 0, 1, 1, 1, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 0, 1, 1, 1, 1, 0, 0} }, { {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 1, 1, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0}, {0, 0, 1, 0, 1, 1, 0, 0}, {0, 1, 0, 0, 1, 1, 0, 0}, {0, 1, 1, 1, 1, 1, 1, 0}, {0, 0, 0, 0, 1, 1, 0, 0}, {0, 0, 0, 0, 1, 1, 0, 0} }, { {0, 0, 0, 0, 0, 0, 0, 0}, {0, 1, 1, 1, 1, 1, 1, 0}, {0, 1, 1, 0, 0, 0, 0, 0}, {0, 1, 1, 1, 1, 1, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 0}, {0, 0, 0, 0, 0, 1, 1, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 0, 1, 1, 1, 1, 0, 0} }, { {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 1, 1, 1, 1, 0, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 1, 1, 0, 0, 0, 0, 0}, {0, 1, 1, 1, 1, 1, 0, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 0, 1, 1, 1, 1, 0, 0} }, { {0, 0, 0, 0, 0, 0, 0, 0}, {0, 1, 1, 1, 1, 1, 1, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 0, 0, 0, 1, 1, 0, 0}, {0, 0, 0, 0, 1, 1, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0} }, { {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 1, 1, 1, 1, 0, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 0, 1, 1, 1, 1, 0, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 0, 1, 1, 1, 1, 0, 0} }, { {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 1, 1, 1, 1, 0, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 0, 1, 1, 1, 1, 1, 0}, {0, 0, 0, 0, 0, 1, 1, 0}, {0, 1, 1, 0, 0, 1, 1, 0}, {0, 0, 1, 1, 1, 1, 0, 0} } }; // scrolls the 'snake' message around the matrix void showSnakeMessage() { [&] { for (int d = 0; d < sizeof(snakeMessage[0]) - 7; d++) { for (int col = 0; col < 8; col++) { delay(messageSpeed); for (int row = 0; row < 8; row++) { // this reads the byte from the PROGMEM and displays it on the screen matrix.setLed(0, row, col, pgm_read_byte(&(snakeMessage[row][col + d]))); } } // if the joystick is moved, exit the message if (analogRead(Pin::joystickY) < joystickHome.y - joystickThreshold || analogRead(Pin::joystickY) > joystickHome.y + joystickThreshold || analogRead(Pin::joystickX) < joystickHome.x - joystickThreshold || analogRead(Pin::joystickX) > joystickHome.x + joystickThreshold) { return; // return the lambda function } } }(); matrix.clearDisplay(0); // wait for joystick co come back while (analogRead(Pin::joystickY) < joystickHome.y - joystickThreshold || analogRead(Pin::joystickY) > joystickHome.y + joystickThreshold || analogRead(Pin::joystickX) < joystickHome.x - joystickThreshold || analogRead(Pin::joystickX) > joystickHome.x + joystickThreshold) {} } // scrolls the 'game over' message around the matrix void showGameOverMessage() { [&] { for (int d = 0; d < sizeof(gameOverMessage[0]) - 7; d++) { for (int col = 0; col < 8; col++) { delay(messageSpeed); for (int row = 0; row < 8; row++) { // this reads the byte from the PROGMEM and displays it on the screen matrix.setLed(0, row, col, pgm_read_byte(&(gameOverMessage[row][col + d]))); } } // if the joystick is moved, exit the message if (analogRead(Pin::joystickY) < joystickHome.y - joystickThreshold || analogRead(Pin::joystickY) > joystickHome.y + joystickThreshold || analogRead(Pin::joystickX) < joystickHome.x - joystickThreshold || analogRead(Pin::joystickX) > joystickHome.x + joystickThreshold) { return; // return the lambda function } } }(); matrix.clearDisplay(0); // wait for joystick co come back while (analogRead(Pin::joystickY) < joystickHome.y - joystickThreshold || analogRead(Pin::joystickY) > joystickHome.y + joystickThreshold || analogRead(Pin::joystickX) < joystickHome.x - joystickThreshold || analogRead(Pin::joystickX) > joystickHome.x + joystickThreshold) {} } // scrolls the 'win' message around the matrix void showWinMessage() { // not implemented yet // TODO: implement it } // scrolls the 'score' message with numbers around the matrix void showScoreMessage(int score) { if (score < 0 || score > 99) return; // specify score digits int second = score % 10; int first = (score / 10) % 10; [&] { for (int d = 0; d < sizeof(scoreMessage[0]) + 2 * sizeof(digits[0][0]); d++) { for (int col = 0; col < 8; col++) { delay(messageSpeed); for (int row = 0; row < 8; row++) { if (d <= sizeof(scoreMessage[0]) - 8) { matrix.setLed(0, row, col, pgm_read_byte(&(scoreMessage[row][col + d]))); } int c = col + d - sizeof(scoreMessage[0]) + 6; // move 6 px in front of the previous message // if the score is < 10, shift out the first digit (zero) if (score < 10) c += 8; if (c >= 0 && c < 8) { if (first > 0) matrix.setLed(0, row, col, pgm_read_byte(&(digits[first][row][c]))); // show only if score is >= 10 (see above) } else { c -= 8; if (c >= 0 && c < 8) { matrix.setLed(0, row, col, pgm_read_byte(&(digits[second][row][c]))); // show always } } } } // if the joystick is moved, exit the message if (analogRead(Pin::joystickY) < joystickHome.y - joystickThreshold || analogRead(Pin::joystickY) > joystickHome.y + joystickThreshold || analogRead(Pin::joystickX) < joystickHome.x - joystickThreshold || analogRead(Pin::joystickX) > joystickHome.x + joystickThreshold) { return; // return the lambda function } } }(); matrix.clearDisplay(0); // // wait for joystick co come back // while (analogRead(Pin::joystickY) < joystickHome.y - joystickThreshold // || analogRead(Pin::joystickY) > joystickHome.y + joystickThreshold // || analogRead(Pin::joystickX) < joystickHome.x - joystickThreshold // || analogRead(Pin::joystickX) > joystickHome.x + joystickThreshold) {} } // standard map function, but with floats float mapf(float x, float in_min, float in_max, float out_min, float out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; }

توسط برنامه بالا می‌توانید بازی Snake را بر روی نمایشگر و به وسیله یک عدد جوی‌استیک اجرا کنید. با حرکت جوی‌استیک می‌توانید حرکت مار را کنترل کنید. غذای مار به صورت دات و نقطه بر روی نمایشگر نشان داده می‌شود و مار با هر بار خوردن هر کدام یک از آنها، بزرگتر می‌شود.
چند نکته در رابطه با برنامه بالا قابل توجه می‌‎باشد:
• پایه های 8، 9 و 10 به عنوان خروجی های آردوینو به نمایشگر ماتریس LED متصل می‌گردند.
• پتانسیومتر برای کنترل سرعت بازی به ورودی آنالوگ A3 متصل می‌گردد.
• محور x جوی‌استیک به A5 و محور y جوی‌استیک به پایه A4 متصل می‌گردد.

گام سوم: مار بازی

حال در این مرحله و پس از آپلود برنامه، می‌توانید بازی را شروع کنید.
با تکان دادن جوی‌استیک، بازی شروع می‌شود. بازی تا جایی ادامه پیدا می‌کند که سر مار با بدنش برخورد نداشته باشد.

فیلم زیر نشان دهنده نحوه و روش انجام بازی می‌باشد:

اجرای بازی Snake با نمایشگر LCD کاراکتری 1602 و آردوینو

این بار همان بازی را توسط یک نمایشگر دیگر اجرا و پیاده‌سازی می‌کنیم.

گام اول: سیم‌بندی

ابتدا توسط مدار زیر قطعات را به آردوینو متصل کنید:

گام دوم: کد برنامه

در ادامه و پس از اتصال آردوینو به کامپیوتر، برنامه زیر را بر روی آردوینوی خود آپلود نمائید :


/*
  Made on 01 Sep 2021
  
Home
*/ #include byte mySnake[8][8] = { { B00000, B00000, B00011, B00110, B01100, B11000, B00000, }, { B00000, B11000, B11110, B00011, B00001, B00000, B00000, }, { B00000, B00000, B00000, B00000, B00000, B11111, B01110, }, { B00000, B00000, B00011, B01111, B11000, B00000, B00000, }, { B00000, B11100, B11111, B00001, B00000, B00000, B00000, }, { B00000, B00000, B00000, B11000, B01101, B00111, B00000, }, { B00000, B00000, B01110, B11011, B11111, B01110, B00000, }, { B00000, B00000, B00000, B01000, B10000, B01000, B00000, } }; boolean levelz[5][2][16] = { {{false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false}, {false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false}}, {{true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true}, {true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true}}, {{true,false,false,false,true,false,false,false,false,false,false,true,false,false,false,true}, {true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,true}}, {{true,false,true,false,false,false,false,false,false,true,false,false,false,true,false,false}, {false,false,false,false,true,false,false,true,false,false,false,true,false,false,false,true}} }; LiquidCrystal lcd(8, 9, 4, 5, 6, 7); unsigned long time, timeNow; int gameSpeed; boolean skip, gameOver, gameStarted; int olddir; int selectedLevel,levels; int adc_key_val[5] ={50, 200, 400, 600, 800 }; int NUM_KEYS = 5; int adc_key_in; int key=-1; int oldkey=-1; boolean x[16][80]; byte myChar[8]; byte nullChar[8] = { 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 }; boolean special; struct partdef { int row,column,dir; //0 - up, 1 - down, 2 - right, 3 - left struct partdef *next; }; typedef partdef part; part *head, *tail; int i,j,collected; long pc,pr; void drawMatrix() { int cc=0; if (!gameOver) { x[pr][pc] = true; //for (i=0;i<8;i++) lcd.createChar(i, nullChar); for(int r=0;r<2;r++) { for(int c=0;c<16;c++) { special = false; for(int i=0;i<8;i++) { byte b=B00000; if (x[r*8+i][c*5+0]) {b+=B10000; special = true;} if (x[r*8+i][c*5+1]) {b+=B01000; special = true;} if (x[r*8+i][c*5+2]) {b+=B00100; special = true;} if (x[r*8+i][c*5+3]) {b+=B00010; special = true;} if (x[r*8+i][c*5+4]) {b+=B00001; special = true;} myChar[i] = b; } if (special) { lcd.createChar(cc, myChar); lcd.setCursor(c,r); lcd.write(byte(cc)); cc++; } else { lcd.setCursor(c,r); if (levelz[selectedLevel][r][c]) lcd.write(255); else lcd.write(254); } } } } } void freeList() { part *p,*q; p = tail; while (p!=NULL) { q = p; p = p->next; free(q); } head = tail = NULL; } void gameOverFunction() { delay(1000); lcd.clear(); freeList(); lcd.setCursor(3,0); lcd.print("Game Over!"); lcd.setCursor(4,1); lcd.print("Score: "); lcd.print(collected); delay(1000); } void growSnake() { part *p; p = (part*)malloc(sizeof(part)); p->row = tail->row; p->column = tail->column; p->dir = tail->dir; p->next = tail; tail = p; } void newPoint() { part *p; p = tail; boolean newp = true; while (newp) { pr = random(16); pc = random(80); newp = false; if (levelz[selectedLevel][pr / 8][pc / 5]) newp=true; while (p->next != NULL && !newp) { if (p->row == pr && p->column == pc) newp = true; p = p->next; } } if (collected < 13 && gameStarted) growSnake(); } void moveHead() { switch(head->dir) // 1 step in direction { case 0: head->row--; break; case 1: head->row++; break; case 2: head->column++; break; case 3: head->column--; break; default : break; } if (head->column >= 80) head->column = 0; if (head->column < 0) head->column = 79; if (head->row >= 16) head->row = 0; if (head->row < 0) head->row = 15; if (levelz[selectedLevel][head->row / 8][head->column / 5]) gameOver = true; // wall collision check part *p; p = tail; while (p != head && !gameOver) // self collision { if (p->row == head->row && p->column == head->column) gameOver = true; p = p->next; } if (gameOver) gameOverFunction(); else { x[head->row][head->column] = true; if (head->row == pr && head->column == pc) // point pickup check { collected++; if (gameSpeed < 25) gameSpeed+=3; newPoint(); } } } void moveAll() { part *p; p = tail; x[p->row][p->column] = false; while (p->next != NULL) { p->row = p->next->row; p->column = p->next->column; p->dir = p->next->dir; p = p->next; } moveHead(); } void createSnake(int n) // n = size of snake { for (i=0;i<16;i++) for (j=0;j<80;j++) x[i][j] = false; part *p, *q; tail = (part*)malloc(sizeof(part)); tail->row = 7; tail->column = 39 + n/2; tail->dir = 3; q = tail; x[tail->row][tail->column] = true; for (i = 0; i < n-1; i++) // build snake from tail to head { p = (part*)malloc(sizeof(part)); p->row = q->row; p->column = q->column - 1; //initial snake id placed horizoltally x[p->row][p->column] = true; p->dir = q->dir; q->next = p; q = p; } if (n>1) { p->next = NULL; head = p; } else { tail->next = NULL; head = tail; } } void startF() { gameOver = false; gameStarted = false; selectedLevel = 1; lcd.clear(); lcd.setCursor(0,0); lcd.print("Select level: 1"); for(i=0;i<8;i++) { lcd.createChar(i,mySnake[i]); lcd.setCursor(i+4,1); lcd.write(byte(i)); } collected = 0; gameSpeed = 8; createSnake(3); time = 0; } void setup() { levels = 5; //number of lvls lcd.begin(16, 2); startF(); } void loop() { if (!gameOver && !gameStarted) { adc_key_in = analogRead(0); // read the value from the sensor key = get_key(adc_key_in); // convert into key press if (key != oldkey) // if keypress is detected { delay(50); // wait for debounce time adc_key_in = analogRead(0); // read the value from the sensor key = get_key(adc_key_in); // convert into key press if (key != oldkey) { oldkey = key; if (key >=0) { olddir = head->dir; if (key==1 && selectedLevel1) selectedLevel--; if (key==4) { lcd.clear(); selectedLevel--; newPoint(); gameStarted = true; } else { lcd.setCursor(14,0); lcd.print(selectedLevel); } } } } } if (!gameOver && gameStarted) { skip = false; //skip the second moveAll() function call if the first was made adc_key_in = analogRead(0); // read the value from the sensor key = get_key(adc_key_in); // convert into key press if (key != oldkey) // if keypress is detected { delay(50); // wait for debounce time adc_key_in = analogRead(0); // read the value from the sensor key = get_key(adc_key_in); // convert into key press if (key != oldkey) { oldkey = key; if (key >=0) { olddir = head->dir; if (key==0 && head->dir!=3) head->dir = 2; if (key==1 && head->dir!=1) head->dir = 0; if (key==2 && head->dir!=0) head->dir = 1; if (key==3 && head->dir!=2) head->dir = 3; if (olddir != head->dir) { skip = true; delay(1000/gameSpeed); moveAll(); drawMatrix(); } } } } if (!skip) { timeNow = millis(); if (timeNow - time > 1000 / gameSpeed) { moveAll(); drawMatrix(); time = millis(); } } } if(gameOver) { adc_key_in = analogRead(0); // read the value from the sensor key = get_key(adc_key_in); // convert into key press if (key != oldkey) // if keypress is detected { delay(50); // wait for debounce time adc_key_in = analogRead(0); // read the value from the sensor key = get_key(adc_key_in); // convert into key press if (key != oldkey) { oldkey = key; if (key >=0) { startF(); } } } } } int get_key(unsigned int input) { int k; for (k = 0; k < NUM_KEYS; k++) { if (input < adc_key_val[k]) { return k; } } if (k >= NUM_KEYS)k = -1; // No valid key pressed return k; }

توسط برنامه بالا می‌توانید بازی Snake را بر روی نمایشگر و به وسیله دکمه های روی ماژول اجرا کنید.

گام سوم: مار بازی

حال در این مرحله و پس از آپلود برنامه، می‌توانید بازی را شروع کنید.
توسط دکمه SELECT و با انتخاب سطح بازی، بازی شروع می‌شود. بازی تا جایی ادامه پیدا می‌کند که سر مار با بدنش برخورد نداشته باشد.

فیلم زیر نشان دهنده نحوه و روش انجام بازی می‌باشد:

یک گام جلوتر

پس از انجام کامل این آموزش، موضوعات و طرح های زیر را می‌توانید به عنوان گام بعدی انجام دهید:
• استفاده از OLED و دیگر LCD های موجود بر روی سایت
• اجرای بازی های دیگر بر روی ماتریس LED با درایور Max7219
• استفاده از ماتریس 8*8 RGB WS2812

آموزش های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد.