Lights Out AD v 1.1

Share your games and programs with the community.

Lights Out AD v 1.1

Postby 94k » Sun Jul 27, 2014 11:14 pm

Lights Out AD
by the indie dev that brought you that other Lights Out clone on some MUSH.

Relive your nostalgic past with the best Lights Out clone on this side of the millennium. With over 250 different board sizes, countless gaming modes (4) and over 7237005577332262213973186563042994240829374041602535252466099000494570602495 different puzzles this game will blow you away. Procedurally generated levels and rougelike gameplay guarantee minutes of fun. Master quantum physics in the SuperPositional mode, bend your brain in "Alternative" mode or explore any of the countless (2) other modes.

Gamemodes:
Classic:
Navigate with the navigation buttons and toggle lights with A, but be careful, each light also toggles the adjacent lights. Turn them all off (i.e. make them a black square) to win.

Alternative
Like Classic mode, only the light you click doesn't toggle (it's adjacent lights still do)

Threeway
Like Classic mode, only the light have a 3rd (dimmed) state which is now included in their cycle.

Super Position
Like Classic mode, only a new super position state is added. A light in super position counts as off, valid solutions might require lights in super positions and off states.

User Reviews:
"Breathtaking" -94k
"A solid 9.314/10" -94k
"A soundtrack that makes you question everything" -94k
"The best Lights Out clone I played in 4 days" -94k

Update:
v 1.1
This is mostly a bugfix update, as well as some smaller adjustments to the main menu.
I also added a score at the end of each game, telling you how long you took, how often you toggled and what mode/size you played (see image).
The code and the v1.1 archive are up to date, for bleeding edge access use git. The contents of the archive are as follows (I also edited the readme slightly):

Code: Select all
LightsOutAD
├── images
│   ├── Game.png
│   ├── InsaneGame.png
│   ├── Menu.png
│   ├── Score.png
│   └── Startscreen.png
├── LIGHTSAD.HEX
├── lightsOutAD
│   └── lightsOutAD.ino
├── lightsOutAD.cpp.elf
└── Readme.txt


Images
Image
Startscreen

Image
Menu with default settings

Image
Standard game

Image
21x12 game in Threeway mode

Image
Score after a successful 5x5 game on Classic.

Image
Actual gameplay

How to get it
git
Code: Select all
git clone git://94k.de/gb


source
Code: Select all
//           DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
//                    Version 2, December 2004
//
// Copyright (C) 2004 Sam Hocevar
//  14 rue de Plaisance, 75014 Paris, France
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
//            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
//   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
//  0. You just DO WHAT THE FUCK YOU WANT TO.
//

#include <SPI.h>
#include <Gamebuino.h>

Gamebuino gb = Gamebuino();

// Helper for drawing the logo
#define PX00 B00000000
#define PX01 B00000110
#define PX10 B01100000
#define PX11 B01100110
#define EMPTYL B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000

// Maximum board sizes
#define XMAX 21
#define YMAX 12
#define RULES 4

// Bitmaps for lights (on, off, superstate)
const byte PROGMEM field[3][6] =
    { {
        4, 4,
        PX00,
        PX10,
        PX10,
        PX00
    } ,{
        4, 4,
        PX00,
        PX00,
        PX00,
        PX00,
    } ,{
        4, 4,
        B01100000,
        B10010000,
        B10010000,
        B01100000
    } };

// Logo
// This feels nasty, but I'm not sure how to do this properly.
const byte PROGMEM screenL[] =
    {
        56, 26,
        EMPTYL,
        EMPTYL,
        EMPTYL,
        EMPTYL,
        EMPTYL,
        EMPTYL,
        EMPTYL,
        PX00, PX11, PX00, PX10, PX00, PX10, PX01,
        PX00, PX11, PX00, PX10, PX00, PX10, PX01,
        EMPTYL,
        EMPTYL,
        PX01, PX00, PX10, PX10, PX10, PX10, PX10,
        PX01, PX00, PX10, PX10, PX10, PX10, PX10,
        EMPTYL,
        EMPTYL,
        PX00, PX11, PX10, PX11, PX10, PX11, PX00,
        PX00, PX11, PX10, PX11, PX10, PX11, PX00,
        EMPTYL,
        EMPTYL,
        PX00, PX00, PX10, PX00, PX10, PX10, PX10,
        PX00, PX00, PX10, PX00, PX10, PX10, PX10,
        EMPTYL,
        EMPTYL,
        PX00, PX11, PX00, PX00, PX10, PX10, PX01,
        PX00, PX11, PX00, PX00, PX10, PX10, PX01,
        EMPTYL
    };

// Board configuration for logo
byte logo[5][14] =
    {
        {1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0},
        {1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
        {1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1},
        {1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1},
        {1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0}
    };


char* ruleSet[] = {"Classic", "Alt.", "Threeway", "SuperPos"};
char* toggleS[] = {"Off", "On"};

// Toggles a given light and the lights around it.
void toggle(char* board, int x, int y, int px, int py, int rule) {
    if(rule < 2) {
        if(px > 0) board[py * x + px - 1] ^= 1;
        if(px < x - 1) board[py * x + px + 1] ^= 1;
        if(py > 0) board[(py - 1) * x + px] ^= 1;
        if(py < y - 1) board[(py + 1) * x + px] ^= 1;
        if(rule == 0) board[py * x + px] ^= 1;
    } else {
        if(px > 0) board[py * x + px - 1] = (board[py * x + px - 1] + 1) % 3;
        if(px < x - 1) board[py * x + px + 1] = (board[py * x + px + 1] + 1) % 3;
        if(py > 0) board[(py - 1) * x + px] = (board[(py - 1) * x + px] + 1) % 3;
        if(py < y - 1) board[(py + 1) * x + px] = (board[(py + 1) * x + px] + 1) % 3;
        board[py * x + px] = (board[py * x + px] + 1) % 3;
    }
}

// Creates a new board with the dimensions x * y
char* newBoard(int x, int y, int rule) {
    char* board = (char *) malloc(x * y);
    // Init board
    for(int i = 0; i < y * x; i++) board[i] = 0;
    if(rule == 3) {
        // Set random fields to super position,
        // the board remains solvable by doing so.
        for(int i = 0; i < x * y; i++) {
            if(random(0,3) == 1) board[i] = 2;
        }
    }
    // Randomize board
    for(int i = 0; i < y; i++) {
        for(int k = 0; k < x; k++) {
            if(random(0,3) == 1) toggle(board, x, y, k, i, rule);
        }
    }
    return board;
}

// Intro animation
void introBoard() {
    int steps = 10;
    int togglesX[steps];
    int togglesY[steps];
    char* board = (char *) malloc(21 * 12);
    // Top non-logo area
    int i, x ,y, h, k;
    for(i = 0; i < 126; i++) board[i] = 1;
    // Set logo
    for(h = 0; h < 5; h++) {
        for(k = 0; k < 14; k++) board[i++] = logo[h][k];
        // Right non-logo area
        for(k = 0; k < 7; k++) board[i++] = 1;
    }
    // Fill the rest
    while(i < 12 * 21) board[i++] = 1;
    i = 0;
    // Randomize board
    for(i = 0; i < steps; i++) {
        // Stay near the logo
        x = random(0, 14);
        y = random(5, 12);
        toggle(board, 21, 12, x, y, 0);
        togglesX[i] = x;
        togglesY[i] = y;
    }
    // Gradually uncover logo
    i = 0; h = 0;
    while(i < steps) {
        if(gb.update()) {
            draw(board, 21, 12, 0, 0);
            if(h++ > 2) {
                h = 0;
                toggle(board, 21, 12, togglesX[i], togglesY[i], 0);
                gb.sound.playTick(); i++;
            }
            if(gb.buttons.pressed(BTN_A)) break;
        }
    }
    // Let it linger for a while
    i = 0;
    while(i < 3) {
        if(gb.update()) {
            draw(board, 21, 12, 0, 0);
            i++;
        }
    }
    free(board);
}

// Checks if the player won and displays a message if they did.
bool won(char* board, int x, int y, int rule, int startT, int toggles) {
    int time = gb.frameCount - startT;
    for(int i = 0; i < y * x; i++) {
        if(rule < 3) {
            if(board[i] != 0) return false;
        } else {
            if(board[i] == 1) return false;
        }
    }
    gb.sound.playOK();
    while(1) {
        if(gb.update()) {
            gb.display.cursorX = 6;
            gb.display.cursorY = 5;
            gb.display.print("You won!");
            gb.display.cursorX = 6;
            gb.display.cursorY += 8;
            gb.display.print(x);
            gb.display.print("x");
            gb.display.print(y);
            gb.display.print(" ");
            gb.display.print(ruleSet[rule]);
            gb.display.cursorX = 6;
            gb.display.cursorY += 8;
            gb.display.print("Time: ");
            gb.display.print(time / 20);
            gb.display.print(".");
            gb.display.print(time % 20 * 5);
            gb.display.print(" s");
            gb.display.cursorX = 6;
            gb.display.cursorY += 8;
            gb.display.print("Toggles: ");
            gb.display.print(toggles);
            gb.display.cursorX = 6;
            gb.display.cursorY += 8;
            gb.display.print("Press A to exit.");
            if(gb.buttons.pressed(BTN_A)) break;
        }
    }
    gb.sound.playOK();
    return true;
}

// Draw board
void draw(char* board, int x, int y, int hOff, int vOff) {
    // Draw borders
    gb.display.drawFastVLine(hOff - 1, vOff - 1, y * 4 + 2);
    gb.display.drawFastHLine(hOff - 1, vOff - 1, x * 4 + 2);
    gb.display.drawFastVLine(hOff + x * 4, vOff, y * 4 + 1);
    gb.display.drawFastHLine(hOff, vOff + y * 4, x * 4 + 0);
    // Draw board
    for(int i = 0; i < y; i++) {
        for(int k = 0; k < x; k++) {
            gb.display.drawBitmap(hOff + k * 4, vOff + i * 4, field[board[i * x + k]]);
        }
    }
}

// The main game.
void game(int x, int y, int rule) {
    int px = 0, py = 0;
    int hOff = (XMAX - x) * 2;
    int vOff = (YMAX - y) * 2;
    char* board = newBoard(x, y, rule);
    int startT = gb.frameCount;
    int toggles = 0;
    if(won(board, x, y, rule, startT, toggles)) {
        free(board);
        return;
    }
    if(x >= 20 && y > 10) gb.battery.show = false;
    while(true) {
        if(gb.update()) {
            draw(board, x, y, hOff, vOff);
            // Draw indicator for position
            gb.display.drawPixel(hOff + px * 4, vOff + py * 4);
            gb.display.drawPixel(hOff + px * 4 + 3, vOff + py * 4);
            gb.display.drawPixel(hOff + px * 4, vOff + py * 4 + 3);
            gb.display.drawPixel(hOff + px * 4 + 3, vOff + py * 4 + 3);
            // Handle buttons
            if(gb.buttons.pressed(BTN_B)) py = 0;
            else if(gb.buttons.pressed(BTN_A)) {
                toggle(board, x, y, px, py, rule);
                toggles++;
                gb.sound.playTick();
                // Check for win and exit (if won).
                if(won(board, x, y, rule, startT, toggles)) break;
            }
            // Movement
            else if(gb.buttons.pressed(BTN_UP)) py = py <= 0 ? y - 1 : py - 1;
            else if(gb.buttons.pressed(BTN_DOWN)) py = py >= y - 1 ? 0 : py + 1;
            else if(gb.buttons.pressed(BTN_LEFT)) px = px <= 0 ? x - 1 : px - 1;
            else if(gb.buttons.pressed(BTN_RIGHT)) px = px >= x - 1 ? 0 : px + 1;
            // Exit
            else if(gb.buttons.pressed(BTN_C)) {
                gb.sound.playCancel();
                break;
            }
        }
    }
    free(board);
    gb.battery.show = true;
}

void setup(){
    gb.begin();
    gb.pickRandomSeed();
    introBoard();
    gb.titleScreen(F("Lights Out AD by"), screenL);
}

void loop() {
    int x = 5, y = 5, pos = 0, rule = 0;
    bool sound = gb.sound.getVolume() > 0 ? 1 : 0;
    while(1) {
        /* Main menu
         * 0. Play
         * 1. Set width
         * 2. Set height
         * 3. Rules
         * 4. Sound
         */
        if(gb.update()) {
            // Draw Menu
            gb.display.drawCircle(6, 7 + pos * 8, 2);
            gb.display.cursorX = 12;
            gb.display.cursorY = 5;
            gb.display.print("Play");
            gb.display.cursorX = 12;
            gb.display.cursorY += 8;
            gb.display.print("Width:  ");
            gb.display.print(x);
            gb.display.cursorX = 12;
            gb.display.cursorY += 8;
            gb.display.print("Height: ");
            gb.display.print(y);
            gb.display.cursorX = 12;
            gb.display.cursorY += 8;
            gb.display.print("Rules:  ");
            gb.display.print(ruleSet[rule]);
            gb.display.cursorX = 12;
            gb.display.cursorY += 8;
            gb.display.print("Sound:  ");
            gb.display.print(toggleS[sound]);
            // Handle buttons
            if(gb.buttons.pressed(BTN_A)) {
                gb.sound.playTick();
                switch(pos) {
                    case 0:
                        game(x, y, rule);
                        break;
                    case 4:
                        (sound ^= true) ? gb.sound.setVolume(VOLUME_GLOBAL_MAX) : gb.sound.setVolume(0);
                        break;
                }
            } else if(gb.buttons.repeat(BTN_LEFT, 4)) {
                switch(pos) {
                    case 1:
                        x = x == 1 ? XMAX : x - 1;
                        gb.sound.playTick();
                        break;
                    case 2:
                        y = y == 1 ? YMAX : y - 1;
                        gb.sound.playTick();
                        break;
                    case 3:
                        rule = rule == 0 ? RULES - 1 : rule - 1;
                        gb.sound.playTick();
                        break;
                }
            } else if(gb.buttons.repeat(BTN_RIGHT, 4)) {
                switch(pos) {
                    case 1:
                        x = max(1, (x + 1) % (XMAX + 1));
                        gb.sound.playTick();
                        break;
                    case 2:
                        y = max(1, (y + 1) % (YMAX + 1));
                        gb.sound.playTick();
                        break;
                    case 3:
                        rule = (rule + 1) % RULES;
                        gb.sound.playTick();
                        break;
                }
            } else if(gb.buttons.pressed(BTN_UP)) {
                gb.sound.playTick();
                // Modulo is somewhat odd for negative numbers
                // maybe it's just me
                pos = pos == 0 ? 4 : pos - 1;
            } else if(gb.buttons.pressed(BTN_DOWN)) {
                gb.sound.playTick();
                pos = (pos + 1) % 5;
            } else if(gb.buttons.pressed(BTN_C)) {
                gb.changeGame();
            }
        }
    }
}


P.s.: There's no images nor .elf/.hex files in the git repo (also no readme). All that stuff is included in the attached archive. However the git version will probably be more up to date. If you want to fix anything or add a feature feel free to send me a .diff file or some branch to check out. I also take bug reports. If you intend to use this code elsewhere please consider the heavily restrictive licence.
Attachments
LightsOutAD.tgz
(79.22 KiB) Downloaded 1520 times
Last edited by 94k on Thu Jul 31, 2014 3:55 pm, edited 3 times in total.
User avatar
94k
 
Posts: 44
Joined: Sun Jul 27, 2014 9:41 pm
Location: Germany

Re: Lights Out AD

Postby treflip » Mon Jul 28, 2014 12:54 pm

I had to try it because the reviews were so high... And the license was right up my ally too!
treflip
 
Posts: 83
Joined: Fri May 30, 2014 4:50 pm
Location: Florida

Re: Lights Out AD

Postby erico » Mon Jul 28, 2014 3:48 pm

oh my...I´m still laughing out on the reviews and license. :D
User avatar
erico
 
Posts: 671
Joined: Thu Mar 27, 2014 9:29 pm
Location: Brazil

Re: Lights Out AD

Postby annyfm » Mon Jul 28, 2014 6:52 pm

It's a hard sell and you nailed it. Cloned from git and compiled, will give it a go soon!
User avatar
annyfm
 
Posts: 22
Joined: Sat Jul 19, 2014 1:31 pm
Location: London, UK

Re: Lights Out AD

Postby Sot0 » Tue Jul 29, 2014 1:34 pm

Sot0
 
Posts: 31
Joined: Sat Apr 26, 2014 3:31 pm
Location: Germany

Re: Lights Out AD

Postby erico » Tue Jul 29, 2014 1:46 pm

Great Hollywood trailer!
I like the intro animation on your game :)
User avatar
erico
 
Posts: 671
Joined: Thu Mar 27, 2014 9:29 pm
Location: Brazil

Re: Lights Out AD

Postby 94k » Tue Jul 29, 2014 2:11 pm

That trailer is blody awesome, thanks Sot0.

@erico: Thank's, I plan to do one for every game I make and have them somewhat relate to the game itself.

I plan to release Lights Out AD 1.1 soonish (sometime this week), including a bunch of bug fixes (for example going up from "Play" puts you on "Sound" rather than "Rules") and some sort of better "You won!" message (most likely including the time taken).
User avatar
94k
 
Posts: 44
Joined: Sun Jul 27, 2014 9:41 pm
Location: Germany

Re: Lights Out AD

Postby Sot0 » Tue Jul 29, 2014 2:51 pm

94k wrote:That trailer is blody awesome, thanks Sot0.



No problem, just got a new Camera and thought thats a good way to use it :lol:

And nice game ;)
Sot0
 
Posts: 31
Joined: Sat Apr 26, 2014 3:31 pm
Location: Germany


Return to Games Gallery

Who is online

Users browsing this forum: No registered users and 15 guests

cron