Skip to content

Instantly share code, notes, and snippets.

@niccokunzmann
Last active March 26, 2016 06:55
Show Gist options
  • Save niccokunzmann/11d0bf0c0bdc2214a4fd to your computer and use it in GitHub Desktop.
Save niccokunzmann/11d0bf0c0bdc2214a4fd to your computer and use it in GitHub Desktop.

LED Table @ FFII

This is a 20x14 Pixels LED table. Each of the pixels can display almost all colors.

You can play the game with the controllers. It is in ffii.ino on this page. You will need to upload it, see Programming. If you would like to see the making of the LED table, you can visit Youtube. If you have any questions, you can leave a comment below or contact me via email.

Usage

To use the LED table you can plug it into the power plug in the wall. If you would like to use the controllers, one of their ends should be plugged into A0 or A1 or A2 or A3 on the Arduino and the other end into Ground or GND or the blue line on the breadboard. You will notice if they are not plugged in correctly: They will not work. In rar cases where you connect 5V and ground directly to eachother, you will melt the cables or destroy the Arduino. Do not do that.

Usually the game snake.ino is loaded onto the Arduino so you can play it. To install the game, read parts of the Programming Section and upload it.

Programming

You can program the LED table yourself using the Arduino software from arduino.cc. Additionally you need to install two Arduino libraries as described in the ledtable library. The default program, a game, can be found here.

Example

The a simple program can look like the following. It first fills the LED table with the color black and then it fills random pixels with random colors.

#include <Adafruit_NeoPixel.h>
#include <ledtable.h>

LEDTable ledtable = LEDTable(2, 20, 14, PIXELORDER<flip_xy, double_spiral>);

void setup()
{
  ledtable.begin();
  ledtable.fill(color_black); // for names see http://html-color-codes.info/color-names/
}

void loop()
{
  // for random see https://www.arduino.cc/en/Reference/Random
  int x = random(ledtable.width());
  int y = random(ledtable.height());  
  ledtable.fill(x, y, random_color());
  ledtable.show();
  delay(50);
}

More Examples

When you have installed the ledtable library, you will notice that examples appear in your Arduino development window under File→Examples→ledtable→... These examples will also work on this LED table but you will need to edit the line of the LED table definition:

LEDTable ledtable = LEDTable(2, 20, 14, PIXELORDER<flip_xy, double_spiral>);

The examples are useful as reference to all functions you can use.

Controllers

If you would like to use the controllers, you need to plug one end of a contoller into ground (GND) and the other end into A0, A1, A2, ... depending on which pin you would like to use. You can try them out with the Arduino example AnalogReadSerial at File→Examples→01.Basics→AnalogReadSerial.


This website is referenced by http://tinyurl.com/ffiiledtable

#include <Adafruit_NeoPixel.h>
#include <ledtable.h>
#include <EEPROM.h>
/*
* Tron - A Game on the LEDTable
*
* Pins
* 2 - The green cable of the LEDTable
* A0 - player 1
* A1 - player 2
* A2 - player 3
* A3 - player 4
* A4 - player 5
*
* Connect the pins to a switch and connect the switch to ground, GND.
* 3______/ ____GND
*
* The following constancs can be changed:
* NUMBER_OF_PLAYERS - the number of players (0, 2, 3, 4, 5, 6, 7, 8)
* STEPS_TO_HOLE - after how many steps there will be a hole in the line
* BACKGROUND_COLOR - the background color of the LEDTable
* TIME_BETWEEN_ROUNDS_MILLIS - how long to wait between frames (milliseconds)
* TIME_BETWEEN_ROUNDS_MILLIS_SPEEDUP - how long to wait between frames if a speedup powerup is taken (milliseconds)
* TIME_BETWEEN_ROUNDS_MILLIS_SLOWDOWN - how long to wait between frames if a slowdown powerup is taken (milliseconds)
* NUMBER_OF_FRAMES_TO_BE_INVULNERABLE - number of steps before the player becomes vulnerable again, if the invulnerability powerup was taken
* EXPECTED_TIME_BETWEEN_POWERUPS - the expected time to wait between powerups showing up
* SPEEDUP_COLOR - the color of the speedup powerup
* SLOWDOWN_COLOR - the color of the slowdown powerup
* INVULNERABLE_COLOR - the color of the invulnerability powerup
*/
#define NUMBER_OF_PLAYERS 4
#define STEPS_TO_HOLE 5
#define BACKGROUND_COLOR color_black
#define TIME_BETWEEN_ROUNDS_MILLIS 300
#define TIME_BETWEEN_ROUNDS_MILLIS_SPEEDUP 200
#define TIME_BETWEEN_ROUNDS_MILLIS_SLOWDOWN 400
#define NUMBER_OF_FRAMES_TO_BE_INVULNERABLE 10
#define EXPECTED_TIME_BETWEEN_POWERUPS 7
#define SPEEDUP_COLOR color_blue
#define SLOWDOWN_COLOR color_orange
#define INVULNERABLE_COLOR color_white
#define COUNT_DOWN_COLOR 0x505050
LEDTable ledtable = LEDTable(2, 20, 14, PIXELORDER<flip_xy, double_spiral>);
//LEDTable ledtable = LEDTable(2, 12, 12, PIXELORDER<snake>);
/* These are private constants. Do not change! */
#define MAXIMUM_NUMBER_OF_PLAYERS 8
int numberOfPlayers = 0;
unsigned int frame;
const int playerPins[MAXIMUM_NUMBER_OF_PLAYERS] = {A0,A1,A2,A3,A4,A5,A6,A7};
const Color colors[MAXIMUM_NUMBER_OF_PLAYERS] = {color_red, color_green, color_yellow, color_violet,
color_orange, color_darkgreen, color_darkgoldenrod, color_white};
enum PlaterState {MOVING, EXPLODING, END_OF_EXPLOSION, DEAD, INVULNERABLE};
enum PlayerDirection {DOWN = 0, LEFT = 1, UP = 2, RIGHT = 3, STANDING};
enum ButtonState {CATCHING_INPUT, WAITING_FOR_PLAYER_TO_SWITCH_BACK};
void letOtherPlayersCollide(int index);
void speedup();
void slowdown();
int roundsWithoutInteraction = 0;
class Player
{
private:
int x;
int y;
int last_x;
int last_y;
uint8_t index;
int8_t nextFillIsBlack;
PlayerDirection direction;
PlayerDirection newDirection;
unsigned int walked;
PlaterState state;
ButtonState inputState;
unsigned int frame_to_switch_moving;
public:
Player()
{
setup();
}
const Color color()
{
return colors[index];
}
const int pin()
{
return playerPins[index];
}
bool leftPressed()
{
// 186 +- 81
int pinValue = analogRead(pin());
return 105 < pinValue && pinValue < 267;
}
bool rightPressed()
{
int pinValue = analogRead(pin());
return pinValue < 105;
}
bool hasInteraction()
{
return rightPressed() || leftPressed();
}
void setup()
{
index = numberOfPlayers;
numberOfPlayers++;
switch (index)
{
case 0: x = 0; y = 0; direction = LEFT; break;
case 1: x = ledtable.maxX(); y = 0; direction = UP; break;
case 2: x = 0; y = ledtable.maxY(); direction = DOWN; break;
case 3: x = ledtable.maxX(); y = ledtable.maxY(); direction = RIGHT; break;
case 4: x = ledtable.middleX() - 1; y = ledtable.middleY(); direction = RIGHT; break;
case 5: x = ledtable.middleX() + 1; y = ledtable.middleY(); direction = LEFT; break;
case 6: x = ledtable.middleX(); y = ledtable.middleY() - 1; direction = DOWN; break;
case 7: x = ledtable.middleX(); y = ledtable.middleY() + 1; direction = UP; break;
}
last_x = x;
last_y = y;
walked = 0;
state = MOVING;
newDirection = direction;
inputState = CATCHING_INPUT;
pinMode(pin(), INPUT);
digitalWrite(pin(), HIGH);
frame_to_switch_moving = 0;
nextFillIsBlack = -1;
paint();
}
void move()
{
last_x = x;
last_y = y;
switch (direction)
{
/* 0 = down, 1 = left, 2 = up, 3 = right, 4 = standing */
case 0: y--; break;
case 1: x++; break;
case 2: y++; break;
case 3: x--; break;
case 4: break;
}
walked++;
}
void update()
{
switch (state)
{
case MOVING: react(); move(); collideWithPlayers(); collideWithObstaclesAndPowerups(); paint(); break;
case INVULNERABLE: react(); move(); invulnerablePosition(); switchToMovingIfPowerupIsOver(); paint(); break;
case EXPLODING: explode(); break;
case END_OF_EXPLOSION: removeExplosion(); break;
case DEAD: break;
}
}
void steppedOnPowerup()
{
nextFillIsBlack = 1;
}
void switchToMovingIfPowerupIsOver()
{
if (frame == frame_to_switch_moving)
{
state = MOVING;
}
}
void collideWithPlayers()
{
letOtherPlayersCollide(index);
}
bool collidesWith(Player* player)
{
return state == MOVING && x_position() == player->x_position() && y_position() == player->y_position();
}
void invulnerablePosition()
{
x = (x + ledtable.width()) % ledtable.width();
y = (y + ledtable.height()) % ledtable.height();
}
void removeExplosion()
{
ledtable.fill(x - 1, y - 1, x + 2, y + 2, BACKGROUND_COLOR);
state = DEAD;
x = -100;
y = -100;
}
void explode() {
ledtable.fill(x - 1, y - 1, x + 2, y + 2, transparent(color(), 120));
state = END_OF_EXPLOSION;
}
void collideWithObstaclesAndPowerups()
{
if (ledtable.isOutside(x, y))
{
collided();
return;
}
Color collided_color = ledtable.at(x, y);
switch (collided_color)
{
case BACKGROUND_COLOR: break;
case INVULNERABLE_COLOR: becomeInvulnerable(); break;
case SPEEDUP_COLOR: speedup(); steppedOnPowerup(); break;
case SLOWDOWN_COLOR: slowdown(); steppedOnPowerup(); break;
default: collided();
}
}
void collided()
{
state = EXPLODING;
}
void react()
{
if (inputState == CATCHING_INPUT)
{
direction = newDirection;
inputState = WAITING_FOR_PLAYER_TO_SWITCH_BACK;
}
}
void paint()
{
Color head = color();
Color tail = transparent(BACKGROUND_COLOR, walked % STEPS_TO_HOLE ? 150 : 0);
if (state == INVULNERABLE)
{
ledtable.fill(last_x, last_y, BACKGROUND_COLOR);
ledtable.fill(x, y, frame_to_switch_moving - frame > 3 ? INVULNERABLE_COLOR : head);
} else {
ledtable.fill(last_x, last_y, tail);
ledtable.fill(x, y, head);
}
if (nextFillIsBlack >= 0)
{
if (nextFillIsBlack == 0)
{
ledtable.fill(last_x, last_y, BACKGROUND_COLOR);
nextFillIsBlack = false;
}
nextFillIsBlack--;
}
}
bool catchInput()
{
bool right = rightPressed();
bool left = leftPressed();
if (inputState == WAITING_FOR_PLAYER_TO_SWITCH_BACK && !right && !left)
{
inputState = CATCHING_INPUT;
}
if (inputState == CATCHING_INPUT)
{
if (left)
{
newDirection = PlayerDirection((direction + 3) % 4);
random(100); random(1223); // create more randomness through players;
}
if (right)
{
newDirection = PlayerDirection((direction + 1) % 4);
random(8);
}
}
if (right || left)
{
roundsWithoutInteraction = 0;
return true;
} else {
return false;
}
}
void becomeInvulnerable()
{
if (state == MOVING || state == INVULNERABLE)
{
state = INVULNERABLE;
frame_to_switch_moving = frame + NUMBER_OF_FRAMES_TO_BE_INVULNERABLE;
}
}
const int x_position()
{
return x;
}
const int y_position()
{
return y;
}
bool isDead()
{
return state == DEAD;
}
};
Player players[8] = { Player(), Player(), Player(), Player(), Player(), Player(), Player(), Player() };
#define FOR_PLAYERS(i) for (int i = 0; i < NUMBER_OF_PLAYERS; i++)
void updatePlayers()
{
FOR_PLAYERS(i)
{
players[i].update();
}
}
void setupPlayers()
{
numberOfPlayers = 0;
FOR_PLAYERS(i)
{
players[i].setup();
}
}
bool catchInput()
{
bool thereWasInteraction = false;
FOR_PLAYERS(i)
{
if (players[i].catchInput())
{
thereWasInteraction = true;
}
}
return thereWasInteraction;
}
void letOtherPlayersCollide(int index)
{
bool collided = false;
for (int i = index + 1; i < NUMBER_OF_PLAYERS; i++)
{
if (players[i].collidesWith(&players[index]) && players[index].collidesWith(&players[i]))
{
players[i].collided();
collided = true;
}
}
if (collided)
{
players[index].collided();
}
}
/********************** Powerups ************************/
int time_between_frames;
void speedup()
{
if (time_between_frames == TIME_BETWEEN_ROUNDS_MILLIS_SLOWDOWN)
{
time_between_frames = TIME_BETWEEN_ROUNDS_MILLIS;
} else {
time_between_frames = TIME_BETWEEN_ROUNDS_MILLIS_SPEEDUP;
}
}
void slowdown()
{
if (time_between_frames == TIME_BETWEEN_ROUNDS_MILLIS_SPEEDUP)
{
time_between_frames = TIME_BETWEEN_ROUNDS_MILLIS;
} else {
time_between_frames = TIME_BETWEEN_ROUNDS_MILLIS_SLOWDOWN;
}
}
#define POWERUP_COLORS 3
Color powerup_colors[POWERUP_COLORS] = { SPEEDUP_COLOR, INVULNERABLE_COLOR, SLOWDOWN_COLOR };
void spawnPowerup()
{
if (random(0, EXPECTED_TIME_BETWEEN_POWERUPS) == 0)
{
int x = random(ledtable.minX(), ledtable.maxX() + 1);
int y = random(ledtable.minY(), ledtable.maxY() + 1);
if (ledtable.at(x, y) == BACKGROUND_COLOR)
{
int choice = random(0, POWERUP_COLORS);
ledtable.fill(x, y, powerup_colors[choice]);
}
}
}
void start()
{
time_between_frames = TIME_BETWEEN_ROUNDS_MILLIS;
frame = 1;
ledtable.fill(BACKGROUND_COLOR);
setupPlayers();
}
void setup()
{
// randomness
uint8_t v = EEPROM.read(0);
for (int i = 0; i < v; i++)
{
random(256);
}
Serial.begin(9600);
ledtable.begin();
//ledtable.brightness(20); // setting brightness makes powerups to obstacles
start();
}
bool wait(int time_to_wait)
{
int now = millis();
int stop = millis() + time_to_wait;
int offset;
if (stop < now)
{
offset = - 2 * time_to_wait;
now += offset;
stop += offset;
} else {
offset = 0;
}
bool thereWasInteraction = false;
for (int i = millis(); i + offset < stop; i = millis())
{
if (catchInput())
{
thereWasInteraction = true;
}
}
return thereWasInteraction;
}
void countDown()
{
for (int x = ledtable.minX(); x <= ledtable.maxX(); x+=2)
{
for (int y = ledtable.minY(); y <= ledtable.maxY(); y++)
{
ledtable.fill(x, y, COUNT_DOWN_COLOR);
ledtable.show();
delay(10);
}
for (int y = ledtable.maxY(); y >= ledtable.minY(); y--)
{
ledtable.fill(x+1, y, COUNT_DOWN_COLOR);
ledtable.show();
delay(10);
}
}
}
void showWaitScreen()
{
while (true)
{
// https://www.arduino.cc/en/Reference/Random
int x1 = random(-1, ledtable.width() + 1);
int y1 = random(-1, ledtable.height() + 1);
int x2 = random(-1, ledtable.width() + 1);
int y2 = random(-1, ledtable.height() + 1);
ledtable.line(x1, y1, x2, y2, ledtable.width() - x1, ledtable.height() - y1, transparent(random_color(), 80));
ledtable.show();
if (wait(80))
{
break;
}
}
}
void restartIfNeeded()
{
int deadPlayers = 0;
FOR_PLAYERS(i)
{
if (players[i].isDead())
{
deadPlayers++;
}
}
if (deadPlayers == NUMBER_OF_PLAYERS)
{
roundsWithoutInteraction++;
if (roundsWithoutInteraction > 2)
{
showWaitScreen();
roundsWithoutInteraction = 0;
} else {
countDown();
}
start();
}
}
void loop()
{
updatePlayers();
spawnPowerup();
ledtable.show();
wait(time_between_frames);
EEPROM.write(0, random(256));
frame++;
restartIfNeeded();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment