User:Decimus Schomer/Projects/Connect-4

From The SchomEmunity Wiki
Jump to: navigation, search

Decimus' user page | Decimus' talk page | Decimus' scripts | Decimus' script libraries | Decimus' projects
Main projects page | Chess | Connect-4


The scripts

Note: As of yet, this does not work!

Controller

// SL Connect-4: Controller script
//
//    Copyright (C) 2007 Decimus Schomer
//
//    This program is free software; you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation; either version 2 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License along
//    with this program; if not, write to the Free Software Foundation, Inc.,
//    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

// Changelog
// * 13th July 2007: Created
// * 14th July 2007: Version 1.0 completed
// * 21st July 2007: Version 1.1 - use linked prims rather than separate ones
// * 27th July 2007: Version 1.1.1 - A minor change to the 'game is set up' messages.
// * 29th July 2007: Version 1.2 - Added detection for when either player wins.
// * 30th July 2007: Version 1.2.1 - Miscellaneous bugfixes and efficiency improvements. Also added draw detection.

string VERSION = "SL Connect-4 version 1.2.1";
integer IN_CHANNEL = 11;

integer st;
integer turn;
integer turns_taken;

key p1;
string pn1;
key p2;
string pn2;

list counters;
list row_heights;

integer buttons;

integer get_move(integer x, integer y)
{
    return llList2Integer(counters, 9*y + x);
}

make_move(integer x, integer y)
{
    integer idx = 9*y + x;
    counters = llListReplaceList(counters, [turn + 1], idx, idx);
    row_heights = llListReplaceList(row_heights, [llList2Integer(row_heights, x) + 1], x, x);
    ++turns_taken;
}

integer get_height(integer x)
{
    return llList2Integer(row_heights, x);
}

integer check_win(integer x)
{
    integer y = get_height(x) - 1;
    integer p = get_move(x, y);
    integer pos;
    integer f;
    integer x1;
    integer y1;
    integer ex;
    ex = 0;
    f = 0;
    for (pos = 1; (pos < 4) && (! f); pos++)
    {
        x1 = x - pos;
        y1 = y - pos;
        if ((x1 < 0) || (y1 < 0))
            f = 1;
        else if (get_move(x1, y1) == p)
            ex++;
        else
            f = 1;
    }
    f = 0;
    for (pos = 1; (pos < 4-ex) && (! f); pos++)
    {
        x1 = x + pos;
        y1 = y + pos;
        if ((x1 > 8) || (y1 > 8))
            f = 1;
        else if (get_move(x1, y) != p)
            f = 1;
    }
    if (! f)
        return p;
    // 1
    ex = 0;
    for (pos = 1; (pos < 4) && (! f); pos++)
    {
        x1 = x + pos;
        y1 = y - pos;
        if ((x1 > 8) || (y1 < 0))
            f = 1;
        else if (get_move(x1, y1) == p)
            ex++;
        else
            f = 1;
    }
    f = 0;
    for (pos = 1; (pos < 4-ex) && (! f); pos++)
    {
        x1 = x - pos;
        y1 = y + pos;
        if ((x1 < 0) || (y1 > 8))
            f = 1;
        else if (get_move(x1, y) != p)
            f = 1;
    }
    if (! f)
        return p;
    // 2
    ex = 0;
    for (pos = 1; (pos < 4) && (! f); pos++)
    {
        x1 = x - pos;
        if (x1 < 0)
            f = 1;
        else if (get_move(x1, y) == p)
            ex++;
        else
            f = 1;
    }
    f = 0;
    for (pos = 1; (pos < 4-ex) && (! f); pos++)
    {
        x1 = x + pos;
        if (x1 > 8)
            f = 1;
        else if (get_move(x1, y) != p)
            f = 1;
    }
    if (! f)
        return p;
    // 3
    ex = 0;
    for (pos = 1; (pos < 4) && (! f); pos++)
    {
        y1 = y - pos;
        if (y1 < 0)
            f = 1;
        else if (get_move(x, y1) == p)
            ex++;
        else
            f = 1;
    }
    if (! f)
        return p;
    // 4
    return 0;
}

rez_buttons()
{
    buttons = 0;
    integer n;
    for (n = 0; n < 9; n++)
        llRezObject("Connect-4 button", llGetPos() + <n-4, 1, 1>, ZERO_VECTOR, ZERO_ROTATION, n);
}

rez_counter(integer x)
{
    integer y = get_height(x);
    if (y == 7)
    {
        llSay(0, "You cannot go there");
        return;
    }
    make_move(x, y);
    if (turn == 0)
    {
        llRezObject("White counter", llGetPos() + <x-4,1,y+2>, ZERO_VECTOR, llEuler2Rot(<270,0,0>*DEG_TO_RAD), 0);
        llSetText("Connect-4: " + pn1 + " vs. " + pn2 + "; " + pn2 + "'s (black's) turn", <0,0,1>, 10);
        turn = 1;
    }
    else
    {
        llRezObject("Black counter", llGetPos() + <x-4,1,y+2>, ZERO_VECTOR, llEuler2Rot(<270,0,0>*DEG_TO_RAD), 0);
        llSetText("Connect-4: " + pn1 + " vs. " + pn2 + "; " + pn1 + "'s (white's) turn", <0,0,1>, 10);
        turn = 0;
    }
    integer i = check_win(x);
    if (i == 0)
    {
        if (turns_taken == 63)
        {
            llSay(0, "Draw!");
            llMessageLinked(LINK_ALL_OTHERS, -1, "disable", NULL_KEY);
            llSetText("Connect-4: " + pn1 + " vs. " + pn2 + "; draw", <0,0,1>, 10);
            st = 3;
        }
        else
            llMessageLinked(LINK_ALL_OTHERS, -1, "turn", NULL_KEY);
    }
    else if (i == 1)
    {
        llSay(0, pn1 + " has won!");
        llMessageLinked(LINK_ALL_OTHERS, -1, "disable", NULL_KEY);
        llSetText("Connect-4: " + pn1 + " vs. " + pn2 + "; " + pn1 + " (whites) has won!", <0,0,1>, 10);
        st = 3;
    }
    else
    {
        llSay(0, pn2 + " has won!");
        llMessageLinked(LINK_ALL_OTHERS, -1, "disable", NULL_KEY);
        llSetText("Connect-4: " + pn1 + " vs. " + pn2 + "; " + pn2 + " (blacks) has won!", <0,0,1>, 10);
        st = 3;
    }
}

init()
{
    llSetText("Connect-4: Idle", <0,0,1>, 10);
    st = 0;
    counters = [
        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];
    row_heights = [0, 0, 0, 0, 0, 0, 0, 0, 0];
    turns_taken = 0;
}

default
{
    state_entry()
    {
        if (! (llGetPermissions() & PERMISSION_CHANGE_LINKS))
            llRequestPermissions(llGetOwner(), PERMISSION_CHANGE_LINKS);
        llListen(IN_CHANNEL, "", "", "");
        init();
    }
    
    touch_start(integer n)
    {
        integer i;
        for (i = 0; i < n; i++)
        {
            llGiveInventory(llDetectedKey(i), "Connect-4 instructions");
        }
    }
    
    listen(integer channel, string name, key id, string msg)
    {
        if (msg == "play")
        {
            if (st == 0)
            {
                p1 = id;
                pn1 = name;
                st = 1;
                llSetText("Connect-4: " + pn1 + " vs. ?", <0,0,1>, 10);
                llSay(0, name + ", you have been added to the game as whites.");
            }
            else if (st == 1)
            {
                p2 = id;
                pn2 = name;
                turn = 0;
                st = 2;
                llSetText("Connect-4: " + pn1 + " vs. " + pn2 + "; setting up game...", <0,0,1>, 10);
                llSay(0, name + ", a game between you and " + pn1 + " is being setup.");
                rez_buttons();
            }
        }
        else if (msg == "quit" && st)
        {
            if (id == p1)
            {
                if (st == 2)
                {
                    llSay(0, "The game between you and " + pn2 + " has been ended.");
                    llInstantMessage(p2, pn1 + " has resigned from the game.");
                }
                else if (st != 3)
                    llSay(0, "You have been removed from this game.");
            }
            else if (st != 3)
            {
                llSay(0, "The game between you and " + pn1 + " has been ended.");
                llInstantMessage(p1, pn2 + " has resigned from the game.");
            }
            llMessageLinked(LINK_ALL_OTHERS, -1, "quit", NULL_KEY);
            llBreakLink(llGetLinkNumber());
            init();
        }
        else if (msg == "version")
            llSay(0, VERSION);
        else if (msg == "license")
            llGiveInventory(id, "Connect-4 License");
    }
    
    link_message(integer sender, integer ob, string msg, key k)
    {
        if (ob != -1)
            rez_counter(ob);
    }
    
    object_rez(key id)
    {
        llCreateLink(id, TRUE);
        if (buttons != -1)
        {
            ++buttons;
            if (buttons == 9)
            {
                llMessageLinked(LINK_ALL_OTHERS, -1, (string)p1, p2);
                buttons = -1;
                llInstantMessage(p1, pn2 + " is playing against you. It is your turn.");
                llInstantMessage(p2, "The game between you and " + pn1 + " has been set up.");
                llSetText("Connect-4: " + pn1 + " vs. " + pn2 + "; " + pn1 + "'s (white's) turn", <0,0,1>, 10);
            }
        }
    }
}

Button script

// SL Connect-4: Button script
//
//    Copyright (C) 2007 Decimus Schomer
//
//    This program is free software; you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation; either version 2 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License along
//    with this program; if not, write to the Free Software Foundation, Inc.,
//    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

// For the changelog, see the controller script

integer pos;
key k1;
key k2;
integer s = 0;
integer turn = 0;

default
{
    on_rez(integer arg)
    {
        pos = arg;
    }
    
    link_message(integer sender, integer ob, string msg, key k)
    {
        if ((msg == "quit") && (pos == 0))
            llSetTimerEvent(1);
        if (msg == "disable")
            s = -1;
        else if ((msg == "turn") && (s == 1))
            turn = turn ^ 1;
        else if (s == 0)
        {
            k1 = (string)msg;
            k2 = k;
            s = 1;
        }
    }

    touch_start(integer n)
    {
        if (s == 1)
        {
            integer x;
            if (turn == 0)
            {
                for (x = 0; x < n; x++)
                {
                    key k = llDetectedKey(x);
                    if (k == k1)
                    {
                        llMessageLinked(LINK_ROOT, pos, "", NULL_KEY);
                    }
                }
            }
            else
            {
                for (x = 0; x < n; x++)
                {
                    key k = llDetectedKey(x);
                    if (k == k2)
                    {
                        llMessageLinked(LINK_ROOT, pos, "", NULL_KEY);
                    }
                }
            }
        }
    }
    
    timer()
    {
        llDie();
    }
}

Instruction card contents

Instructions for SL Connect-4
==================

These instructions assume you already know how to play connect-4; they simply tell you how to use this version, specially made for SL

How to interpret the floating text
-------------------------------------

If you look at the text above the controller object, it will say 'Connect-4:' followed by either (things in angle brackets ('<' and '>') mean something of that type, *not* the literal text):
* 'Idle' (meaning that nobody is currently playing)
* '<name> vs. ?' (a literal question mark) if a person has chosen to play but no opponents are playing them
* '<name> vs. <name>; setting up...' when a game is being set up.
* '<name> vs. <name>; <name>'s (<white's/black's>) turn' when a game is in progress
* '<name> vs. <name>; <name> (<whites/blacks>) has won' when someone won the game
* '<name> vs. <name>; draw' when the game was a draw.

Commands
------------

All commands must be said on channel 11. This means that you have to prefix them with '/11'. These are case-sensitive!

'play' - Unless a game is currently in progress, this will add you to a new game.
'quit' - If you are playing a game, this will make you resign. Currently, there is no feature to detect when someone has won, so when someone does win one of the players will have to say this.
'version' - Say this to find out the version number
'license' - Get a copy of the notecard containing the GNU GPL, which SL Connect-4 is licensed under

How to play
-------------

Once both players have joined the game (Commands->'play' above), a series of nine tiles will appear. The controller's floating text will indicate whose turn it is, and that person must touch the tile under the column they want to play in.

How to use

To use these scripts, you will need to make an object containing:

  • The controller script
  • An object called 'Connect-4 button' which contains the button script and is <1m on its X and Z dimensions
  • An object called 'Black counter' and another called 'White counter', each <1m in X and Y, containing no scripts.
  • A notecard called 'Connect-4 instructions', containing the text under the 'instruction card contents' heading above.
  • A notecard called 'Connect-4 license', containing the GPL (v2).

Or, if you can't be bothered doing that, you can go to here, in SP, and copy the 'Connect-4 controller' object there ;)