Codici
Colossal Cave

Colossal Cave Adventure

Benché oggi quasi totalmente dimenticato, questo è il primo text adventure game dell'universo conosciuto, e ha dato origine al più famoso Zork e a tanti altri giochi.Da questo gioco derivano dei modi dire che sono diventati famosi nel mondo della programmazione, come "You are in a maze of twisty little passages, all alike" oppure la parola magica Xyzzy o il grido PLUGH!.

La versione originale di CCA (in breve: Advent) è stata scritta da Will Crowther, che oltre a lavorare come programmatore alla Bolt, Beranek & Newman aveva altri interessi (come molti programmatori). Il più importante era la speleologia: Will amava le caverne e le esplorava insieme alla moglie. L'altro interesse di Will erano i giochi da tavolo come Dungeons and Dragons.

Non ci voleva molto perché i due interessi collassassero in uno: la creazione di un videogioco in cui il giocatore doveva esplorare una caverna labirintica per trovare tesori.

Una caratteristica fondamentale del gioco era che il programma interagiva col giocatore in inglese: era capace di "capire" frasi composte da un verbo e da un complemento e di rispondere. In totale, riconosceva solo 200 parole. Va detto che era uno dei primi software al mondo ad esserne capace.

Il tutto avveniva nel 1975 e con un computer che avete probabilmente giù incontrato nella mostra: il PDP-10. Non era un personal computer, tutt'altro. Il suo sistema operativo era di quelli a Time sharing: ovvero costava talmente tanto che molti utenti avevano accesso allo stesso computer, mettevano in coda i proprio programmi e aspettavano il loro turno per l'esecuzione.

La versione originale di CCA era scritta in 700 righe FORTRAN - un linguaggio nato per i calcoli, non certo per i videogiochi. Era composta da una sezione dati, con le parole e le descrizioni delle stanze, e una delle istruzioni.

	DO 1001 I=1,300
	IF(I.LE.100)PTEXT(I)=0
	IF(I.LE.RTXSIZ)RTEXT(I)=0
	IF(I.LE.CLSMAX)CTEXT(I)=0
	IF(I.LE.MAGSIZ)MTEXT(I)=0
	IF(I.GT.LOCSIZ)GOTO 1001
	STEXT(I)=0
	LTEXT(I)=0
	COND(I)=0
	KEY(I)=0
1001	CONTINUE

	CALL IFILE(1,'TEXT')
	SETUP=1
	LINUSE=1
	TRVS=1
	CLSSES=1

C  START NEW DATA SECTION.  SECT IS THE SECTION NUMBER.

1002	READ(1,1003)SECT
1003	FORMAT(G)
	OLDLOC=-1
	GOTO(1100,1004,1004,1030,1040,1004,1004,1050,1060,1070,1004,
	1    1080,1004) (SECT+1)
C	      (0)  (1)  (2)  (3)  (4)  (5)  (6)  (7)  (8)  (9)  (10)
C	     (11) (12)
	CALL BUG(9)

Un apporto importante fu quello di Don Woods, uno laureato a Stanford capitato per caso su CCA, che aggiunse funzionalità e profondità al gioco, nonché un retrogusto tolkeniano. Ma soprattutto, rese disponibili i sorgenti del suo programma.

Per valutare l'importanza di questo gioco nel mondo della programmazione si può leggere l'articolo che gli dedica Donald Knuth, in cui ricorda come il gioco gli fosse stato presentato nel 1977 nientepopodimeno che da John McCarthy e come, 21 anni dopo, gli fosse venuta voglia di riscriverlo in CWEB, il suo personale ambiente/linguaggio di programmazione.

CCA fu riprodotto per una quantità di sistemi operativo e computer, tra cui l'MS-DOS.

Tra quelli che riconobbero l'importanza di CCA va sicuramente citato Eric Steven Raymond, che è conosciuto per altre cose. Nel 2017 Raymond scrive un porting di CCA in C, che può essere installato anche oggi su un sistema Linux, Windows o Mac con pochi passaggi.

/*
 * Actions for the duneon-running code.
 *
 * Copyright (c) 1977, 2005 by Will Crowther and Don Woods
 * Copyright (c) 2017 by Eric S. Raymond
 * SPDX-License-Identifier: BSD-2-clause
 */

#include 
#include 
#include 
#include "advent.h"
#include "dungeon.h"
#include 

static phase_codes_t fill(verb_t, obj_t);

static phase_codes_t attack(command_t command)
/*  Attack.  Assume target if unambiguous.  "Throw" also links here.
 *  Attackable objects fall into two categories: enemies (snake,
 *  dwarf, etc.)  and others (bird, clam, machine).  Ambiguous if 2
 *  enemies, or no enemies but 2 others. */
{
    verb_t verb = command.verb;
    obj_t obj = command.obj;

    if (obj == INTRANSITIVE) {
        int changes = 0;
        if (atdwrf(game.loc) > 0) {
            obj = DWARF;
            ++changes;
        }
        if (HERE(SNAKE)) {
            obj = SNAKE;
            ++changes;
        }
        if (AT(DRAGON) && game.prop[DRAGON] == DRAGON_BARS) {
            obj = DRAGON;
            ++changes;
        }
        if (AT(TROLL)) {
            obj = TROLL;
            ++changes;
        }
        if (AT(OGRE)) {
            obj = OGRE;
            ++changes;
        }
        if (HERE(BEAR) && game.prop[BEAR] == UNTAMED_BEAR) {
            obj = BEAR;
            ++changes;
        }
        /* check for low-priority targets */
        if (obj == INTRANSITIVE) {
            /* Can't attack bird or machine by throwing axe. */
            if (HERE(BIRD) && verb != THROW) {
                obj = BIRD;
                ++changes;
            }
            if (HERE(VEND) && verb != THROW) {
                obj = VEND;
                ++changes;
            }
            /* Clam and oyster both treated as clam for intransitive case;
             * no harm done. */
            if (HERE(CLAM) || HERE(OYSTER)) {
                obj = CLAM;
                ++changes;
            }
        }
        if (changes >= 2)
            return GO_UNKNOWN;
    }

    if (obj == BIRD) {
        if (game.closed) {
            rspeak(UNHAPPY_BIRD);
        } else {
            DESTROY(BIRD);
            rspeak(BIRD_DEAD);
        }
        return GO_CLEAROBJ;
    }
    if (obj == VEND) {
        state_change(VEND,
                     game.prop[VEND] == VEND_BLOCKS ? VEND_UNBLOCKS : VEND_BLOCKS);

        return GO_CLEAROBJ;
    }

    if (obj == BEAR) {
        switch (game.prop[BEAR]) {
        case UNTAMED_BEAR:
            rspeak(BEAR_HANDS);
            break;
        case SITTING_BEAR:
            rspeak(BEAR_CONFUSED);
            break;
        case CONTENTED_BEAR:
            rspeak(BEAR_CONFUSED);
            break;
        case BEAR_DEAD:
            rspeak(ALREADY_DEAD);
            break;
        }
        return GO_CLEAROBJ;
    }
    if (obj == DRAGON && game.prop[DRAGON] == DRAGON_BARS) {
        /*  Fun stuff for dragon.  If he insists on attacking it, win!
         *  Set game.prop to dead, move dragon to central loc (still
         *  fixed), move rug there (not fixed), and move him there,
         *  too.  Then do a null motion to get new description. */
        rspeak(BARE_HANDS_QUERY);
        if (!silent_yes()) {
            speak(arbitrary_messages[NASTY_DRAGON]);
            return GO_MOVE;
        }
        state_change(DRAGON, DRAGON_DEAD);
        game.prop[RUG] = RUG_FLOOR;
        /* Hardcoding LOC_SECRET5 as the dragon's death location is ugly.
         * The way it was computed before was worse; it depended on the
         * two dragon locations being LOC_SECRET4 and LOC_SECRET6 and
         * LOC_SECRET5 being right between them.
         */
        move(DRAGON + NOBJECTS, IS_FIXED);
        move(RUG + NOBJECTS, IS_FREE);
        move(DRAGON, LOC_SECRET5);
        move(RUG, LOC_SECRET5);
        drop(BLOOD, LOC_SECRET5);
        for (obj_t i = 1; i <= NOBJECTS; i++) {
            if (game.place[i] == objects[DRAGON].plac ||
                game.place[i] == objects[DRAGON].fixd)
                move(i, LOC_SECRET5);
        }
        game.loc = LOC_SECRET5;
        return GO_MOVE;
    }

    if (obj == OGRE) {
        rspeak(OGRE_DODGE);
        if (atdwrf(game.loc) == 0)
            return GO_CLEAROBJ;

        rspeak(KNIFE_THROWN);
        DESTROY(OGRE);
        int dwarves = 0;
        for (int i = 1; i < PIRATE; i++) {
            if (game.dloc[i] == game.loc) {
                ++dwarves;
                game.dloc[i] = LOC_LONGWEST;
                game.dseen[i] = false;
            }
        }
        rspeak((dwarves > 1) ?
               OGRE_PANIC1 :
               OGRE_PANIC2);
        return GO_CLEAROBJ;
    }

    switch (obj) {
    case INTRANSITIVE:
        rspeak(NO_TARGET);
        break;
    case CLAM:
    case OYSTER:
        rspeak(SHELL_IMPERVIOUS);
        break;
    case SNAKE:
        rspeak(SNAKE_WARNING);
        break;
    case DWARF:
        if (game.closed) {
            return GO_DWARFWAKE;
        }
        rspeak(BARE_HANDS_QUERY);
        break;
    case DRAGON:
        rspeak(ALREADY_DEAD);
        break;
    case TROLL:
        rspeak(ROCKY_TROLL);
        break;
    default:
        speak(actions[verb].message);
    }
    return GO_CLEAROBJ;
}

Prossimo pannello:  Zork