// Adapted from https://wokwi.com/projects/318864638990090834 #include WiFiServer server(80); // const char* ssid = "MIT"; // Replace with your network SSID // const char* password = "&YbtLSS$C8"; // Replace with your network password const char* ssid = "2048";// Replace with your network SSID const char* password = "23333333"; // Replace with your network password #define CLK 13 #define DIN 11 #define CS 10 #define X_SEGMENTS 4 #define NUM_SEGMENTS (X_SEGMENTS * X_SEGMENTS) #define REFRESH_RATE 80 #define ROLLING_GAP 4 byte fb[8 * NUM_SEGMENTS]; uint16_t game[16]; uint32_t score = 0; bool moved = false; bool game_over = false; uint8_t current_x = 0; uint8_t current_y = 0; void setup() { Serial.begin(115200); WiFi.beginAP(ssid, password); server.begin(); pinMode(CLK, OUTPUT); pinMode(DIN, OUTPUT); pinMode(CS, OUTPUT); // Setup MAX7219 shiftAll(0x0f, 0x00); //display test register - test mode off shiftAll(0x0b, 0x07); //scan limit register - display digits 0 thru 7 shiftAll(0x0c, 0x01); //shutdown register - normal operation shiftAll(0x0a, 0x0f); //intensity register - max brightness shiftAll(0x09, 0x00); //decode mode register - No decode init_game(); Serial.print(WiFi.softAPIP()); } void loop() { receive_request(); unsigned long frame = int(millis() / REFRESH_RATE); for (int i=0;i<16;i++) { draw_number(i / X_SEGMENTS, i % X_SEGMENTS, game[i], frame); } show(); } // -------- Game void init_game() { for (int i=0;i<16;i++) { game[i] = 0; } for (int i=0;i<16;i++) { Serial.print(game[i]); Serial.print(" "); } Serial.println(); score = 0; moved = false; game_over = false; spawn_new_cell(); } bool spawn_new_cell() { byte count = 0; for (int i=0;i<16;i++) { if (game[i] == 0) { count++; } } if (count == 0) { return false; } byte pick = random(count); for (int i=0;i<16;i++) { if (game[i] == 0) { if (pick == 0) { game[i] = random(5) == 0 ? 4 : 2; break; } else { pick--; } } } return true; } void rotate_board() { uint16_t temp[16]; for (int i = 0; i < 16; i++) { int row = i / 4; int col = i % 4; temp[col * 4 + (3 - row)] = game[i]; } for (int i = 0; i < 16; i++) { game[i] = temp[i]; } } void slide_and_merge_row(uint16_t row[4]) { int last_merged = -1; for (int i = 1; i < 4; i++) { if (row[i] == 0) continue; int j = i; while (j > 0 && row[j - 1] == 0) { row[j - 1] = row[j]; row[j] = 0; j--; moved = true; } if (j > 0 && row[j - 1] == row[j] && last_merged != j - 1) { row[j - 1] *= 2; row[j] = 0; last_merged = j - 1; score += row[j - 1]; moved = true; } } } void slide_and_merge(int direction) { for (int i = 0; i < direction; i++) { rotate_board(); } for (int i = 0; i < 4; i++) { uint16_t row[4] = { game[i * 4], game[i * 4 + 1], game[i * 4 + 2], game[i * 4 + 3] }; slide_and_merge_row(row); for (int j = 0; j < 4; j++) { game[i * 4 + j] = row[j]; } } for (int i = 0; i < (4 - direction) % 4; i++) { rotate_board(); } } // -------- Communication void receive_request() { WiFiClient client = server.available(); if (client) { String currentLine = ""; Serial.println("Connected!"); while (client.connected()) { if (client.available()) { char c = client.read(); if (c == '\n') { if (currentLine.length() != 0) { int charIndex = currentLine.indexOf("command="); if (charIndex != -1) { char receivedChar = currentLine.charAt(charIndex + 8); process_command(receivedChar); } currentLine = ""; } else { client.println("HTTP/1.1 200 OK"); client.println("Content-type:text/html"); client.println(); if (!moved) { client.println("Not moved!"); } else { client.print("Moved! Current score: "); client.println(score); } moved = false; if (game_over) { client.println("Gameover! Press R to restart."); } break; } } else if (c != '\r') { currentLine += c; } } } client.stop(); } } void process_command(char command) { if (command == 'R') { init_game(); } else { byte dir; switch (command) { case 'W': dir = 3; break; case 'S': dir = 1; break; case 'A': dir = 0; break; case 'D': dir = 2; break; } slide_and_merge(dir); if (moved && !spawn_new_cell()) { game_over = true; } } } // -------- Display void shiftAll(byte send_to_address, byte send_this_data) { digitalWrite(CS, LOW); for (int i = 0; i < NUM_SEGMENTS; i++) { shiftOut(DIN, CLK, MSBFIRST, send_to_address); shiftOut(DIN, CLK, MSBFIRST, send_this_data); } digitalWrite(CS, HIGH); } void show() { for (byte row = 0; row < 8; row++) { digitalWrite(CS, LOW); byte segment = NUM_SEGMENTS; while (segment--) { byte x = segment % X_SEGMENTS; byte y = segment / X_SEGMENTS * 8; byte addr = (row + y) * X_SEGMENTS; if (segment & X_SEGMENTS) { // odd rows of segments shiftOut(DIN, CLK, MSBFIRST, 8 - row); shiftOut(DIN, CLK, LSBFIRST, fb[addr + x]); } else { // even rows of segments shiftOut(DIN, CLK, MSBFIRST, 1 + row); shiftOut(DIN, CLK, MSBFIRST, fb[addr - x + X_SEGMENTS - 1]); } } digitalWrite(CS, HIGH); } } void draw_number(uint8_t x, uint8_t y, uint16_t num, unsigned long frame) { if (num == 0) draw_digit(x, y, 255); else if (num < 10) draw_digit(x, y, num); else { uint8_t count = 0; byte digits[5][8]; while (num > 0) { byte representation[8]; get_representation(representation, num % 10); for (int d=0;d<8;d++) { digits[count][d] = representation[d]; } count++; num /= 10; } for (byte row=0;row<8;row++) { fb[x * X_SEGMENTS * 8 + y + row * X_SEGMENTS] = B00000000; for (byte d=1;d<8;d++) { byte window_pos = (d + frame) % (7 * count + ROLLING_GAP); byte digit = window_pos / 7; if (digit < count) { digit = count - digit - 1; bool bit_value = (digits[digit][row] >> (7 - (window_pos % 7))) & 1; fb[x * X_SEGMENTS * 8 + y + row * X_SEGMENTS] |= (bit_value << (7 - d)); } } } } } void draw_digit(uint8_t x, uint8_t y, uint8_t digit) { if ((x >= X_SEGMENTS) || (y >= X_SEGMENTS) || (digit > 9 && digit != 255)) return; byte representation[8]; get_representation(representation, digit); for (byte i = 0; i < 8; i++) { fb[x * X_SEGMENTS * 8 + y + i * X_SEGMENTS] = representation[i]; } } void get_representation(byte representation[8], uint8_t digit) { //Use 255 to represent empty cell switch (digit) { case 0: representation[0] = B00000000; representation[1] = B00111100; representation[2] = B01100110; representation[3] = B01100110; representation[4] = B01100110; representation[5] = B01100110; representation[6] = B01100110; representation[7] = B00111100; break; case 1: representation[0] = B00000000; representation[1] = B00111000; representation[2] = B00011000; representation[3] = B00011000; representation[4] = B00011000; representation[5] = B00011000; representation[6] = B00011000; representation[7] = B00011000; break; case 2: representation[0] = B00000000; representation[1] = B00111100; representation[2] = B01100110; representation[3] = B00000110; representation[4] = B00001100; representation[5] = B00111000; representation[6] = B01100000; representation[7] = B01111110; break; case 3: representation[0] = B00000000; representation[1] = B00111100; representation[2] = B01100110; representation[3] = B00000110; representation[4] = B00001100; representation[5] = B00000110; representation[6] = B01100110; representation[7] = B00111100; break; case 4: representation[0] = B00000000; representation[1] = B01100110; representation[2] = B01100110; representation[3] = B01100110; representation[4] = B01111110; representation[5] = B00000110; representation[6] = B00000110; representation[7] = B00000110; break; case 5: representation[0] = B00000000; representation[1] = B01111110; representation[2] = B01100000; representation[3] = B01100000; representation[4] = B01111100; representation[5] = B00000110; representation[6] = B01100110; representation[7] = B00111100; break; case 6: representation[0] = B00000000; representation[1] = B00111100; representation[2] = B01100110; representation[3] = B01100000; representation[4] = B01111100; representation[5] = B01100110; representation[6] = B01100110; representation[7] = B00111100; break; case 7: representation[0] = B00000000; representation[1] = B01111110; representation[2] = B00000110; representation[3] = B00001100; representation[4] = B00011000; representation[5] = B00011000; representation[6] = B00011000; representation[7] = B00011000; break; case 8: representation[0] = B00000000; representation[1] = B00111100; representation[2] = B01100110; representation[3] = B01100110; representation[4] = B00111100; representation[5] = B01100110; representation[6] = B01100110; representation[7] = B00111100; break; case 9: representation[0] = B00000000; representation[1] = B00111100; representation[2] = B01100110; representation[3] = B01100110; representation[4] = B00111110; representation[5] = B00000110; representation[6] = B01100110; representation[7] = B00111100; break; case 255: representation[0] = B00000000; representation[1] = B00000000; representation[2] = B00000000; representation[3] = B00000000; representation[4] = B00000000; representation[5] = B00000000; representation[6] = B00000000; representation[7] = B00000000; break; } }