David Liu

Expedition: The Beckett Endeavour

About the Game

Expedition: The Beckett Endeavour is a co-op puzzle game. Two players are needed in order to complete the game: one as a Researcher and the other as the Explorer. Both players must communicate between them on the room or clues they find in order to solve each puzzle. For the Researcher role, their focus is primarily on deciphering clues in order to communicate to the Explorer in what they need to do. The Explorer's role will execute out the communicated actions of the Researcher, and respond whether it worked as it would have intended. It is essential for both players to communicate clearly though, as miscommunication is a common downfall as designed in the game.

Developed in Global Game Jam 2018

Play the game here!

Role in Development

  • Designer: Tiger Louck, David Liu
  • Programmers: Tiger Louck, David Liu
  • Artist: Samuel Keir
  • SFX Artist: Max De George
  • Music: Ka Man Chang

Having started this jam shortly after learning about different algorithms in my Data Structures and Algorithms class, I wanted to try and put those algorithms to use in this game. I designed two of the puzzles for the game: one about placing a box down onto pressure plates in a specific order, and one about pressing the correct runes in a specific order. The second puzzle is unique in the way that it randomly generates a set of runes after each sequence with multiple different sequences at the start. This way, the player cannot memorize the order since the sequence is always changing. I wanted to try and make that puzzle use a dynamically expanding tree that uses a depth-first search, but I was unable to make it dynamically expandable. Instead, I ended up having to create the tree manually. However, I still found this project very helpful in learning more about the use of algorithms and structures.

Script Examples

PuzzleMaster.csInteractInput.csColorButton.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/*
 * Tiger Louck, David Liu, Sam Keir
 * puzzle master game controller script, handles the pressure plate combo lock, the scale, and the color tree puzzles.
 */
public class PuzzleMaster : MonoBehaviour {

    private int[] plateCombo = new int[6] { 2, 3, 3, 0, 4, 3 };
    private int lockState = 0;
    public AudioSource crateSound;
    public AudioSource plateSound;
    public AudioSource doorSound;
    public float soundVariationRange;
    private float startPitch;

    #region Sand Puzzle Variables
    private int sandWeight = 10;
    private int sandCap = 20;
    private int sandAnswer = 17;

    public AudioSource sandSound;

    // Reference each object and script
    public GameObject sandBagObject;
    public GameObject rockScaleObject;
    public SandButton sandButton;
    public SandBag sandBag;
    public SandPlate sandPlate;
    public SandBagHold sandBagHold;
    public GameObject ScaleArm;
    public float heightChange = 0.005f;
    public AudioSource sandBagDropSound;
    public GameObject SandTossPrefab;
    #endregion

    #region Color Puzzle Variables
    public AudioSource buttonSound;
    public float soundVariationRange2;
    private float startPitch2;
    public int patternLength;
    //public List allColors = new List();
    public Sprite[] allColorPaths;
    public List> colorCodes = new List>();
    public int codePosition = 0;
    public int position = 0;
    public bool isWrong = false;
    public bool isCompleted = false;
    #endregion

    public GameObject exit;

    public void Start()
    {
        #region Color Puzzle
        // Failed Recursion Algorithm for expandable tree
        /*(int result = 1;
        result = (int)(Mathf.Pow(3, patternLength - 1) * patternLength);
        allColorPaths = new Sprite[result];
        for(int j = 0; j < allColors.Count; j++)
            RecurseColors(result, 0, 0, -1, 0, true);*/

        for (int i = 0; i < allColorPaths.Length;)
        {
            List colorList = new List();
            colorCodes.Add(colorList);
            for (int j = 0; j < patternLength; j++)
            {
                Debug.Log(allColorPaths[i]);
                colorList.Add(allColorPaths[i]);
                i++;
            }
            Debug.Log("NEW CODE");
        }

        codePosition = Random.Range(0, colorCodes.Count);

        if (buttonSound != null) startPitch2 = buttonSound.pitch;
        #endregion

        #region Plate Puzzle
        if (crateSound != null) startPitch = crateSound.pitch;
        #endregion
    }

    public void HitPlate(int plateCode)
    {
        Debug.Log(plateCode);

        // Add variation in pitch to the sound effect
        float num = Random.Range(0, soundVariationRange);
        int num2 = Random.Range(0, 2);
        if (num2 == 0) crateSound.pitch = startPitch - num;
        else crateSound.pitch = startPitch + num;
        crateSound.Play();
        plateSound.Play();

        if (plateCode == plateCombo[lockState])
            lockState++;
        else lockState = 0;

        if (lockState == plateCombo.Length)
        {
            exit.SendMessage("Open");
            doorSound.Play();
        }
            
    }

    #region Sand Puzzle Methods
    // Adds sand to the bag
    public void AddSand()
    {
        if (sandWeight != sandCap && !sandBagHold.isHeld && !sandPlate.onPlate)
        {
            // Add variation in pitch to the sound effect
            float num = Random.Range(0, soundVariationRange2);
            int num2 = Random.Range(0, 2);
            if (num2 == 0) buttonSound.pitch = startPitch2 - num;
            else buttonSound.pitch = startPitch2 + num;
            buttonSound.Play();

            sandWeight++;
            ScaleArm.transform.rotation *= Quaternion.Euler(0, 0, 1);
            rockScaleObject.transform.rotation = Quaternion.Euler(0,0,-1);
            sandBag.transform.rotation = Quaternion.Euler(0, 0, -1);
            sandBagObject.transform.rotation = Quaternion.Euler(0, 0, -1);
        }
    }

    // Removes sand from the bag
    public void RemoveSand()
    {
        if (sandWeight != 0 && !sandBagHold.isHeld && !sandPlate.onPlate)
        {
            sandSound.Play();
            sandWeight--;
            ScaleArm.transform.rotation *= Quaternion.Euler(0, 0, -1);
            rockScaleObject.transform.rotation = Quaternion.Euler(0, 0, 1);
            sandBag.transform.rotation = Quaternion.Euler(0, 0, 1);
            sandBagObject.transform.rotation = Quaternion.Euler(0, 0, 1);
            GameObject.Destroy( Instantiate(SandTossPrefab,sandBag.sandBag.transform, true), 1);
        }
    }

    // Places bag on plate and tests the code
    public void TestSand()
    {
        if (sandBagHold.isHeld)
        {
            sandBagDropSound.Play();
            plateSound.Play();
            if (sandWeight == sandAnswer)
            {
                exit.SendMessage("Open");
                doorSound.Play();
            }
            sandBagHold.isHeld = false;
            sandPlate.onPlate = true;
            sandBagObject.transform.position = sandPlate.transform.position;
            sandPlate.GetComponent().SetBool("PlateDown", true);
        }
        else if(sandPlate.onPlate)
        {
            sandBagHold.isHeld = true;
            sandPlate.onPlate = false;
            sandPlate.GetComponent().SetBool("PlateDown", false);
        }
    }

    // Places bag visibly on the scale part
    public void PlaceBagScale()
    {
        sandBagHold.isHeld = false;
        sandBagObject.transform.position = sandBag.transform.position + Vector3.up * -.067f;
    }

    // Makes the player hold the sand bag
    public void HoldBag()
    {
        sandBagHold.isHeld = true;
        sandPlate.onPlate = false;
    }
    #endregion

    #region Color Puzzle Methods
    // Receives input from a color button
    public void InputColor(List> colors)
    {
        // Add variation in pitch to the sound effect
        float num = Random.Range(0, soundVariationRange2);
        int num2 = Random.Range(0, 2);
        if (num2 == 0) buttonSound.pitch = startPitch2 - num;
        else buttonSound.pitch = startPitch2 + num;
        buttonSound.Play();

        if (colors[codePosition][position] == colorCodes[codePosition][position]) position++;
        else isWrong = true;

        // Check if code is completed
        if (position >= colors[codePosition].Count)
        {
            exit.SendMessage("Open");
            doorSound.Play();
            isCompleted = true;
        }
    }

    // Resets the puzzle to the start of the code
    public void ResetColorPuzzle()
    {
        // Add variation in pitch to the sound effect
        float num = Random.Range(0, soundVariationRange2);
        int num2 = Random.Range(0, 2);
        if (num2 == 0) buttonSound.pitch = startPitch2 - num;
        else buttonSound.pitch = startPitch2 + num;
        buttonSound.Play();

        isWrong = false;
        if (!isCompleted)
        {
            position = 0;
            codePosition = Random.Range(0, colorCodes.Count);
        }
    }

    /*public void RecurseColors(int count, int path, int counter, int index, int prevStart, bool firstRecursion)
    {
        if (!firstRecursion) count = count / 3;
        if (count == 1) return;
        else
        {
            switch (path)
            {
                case 0:
                    index++;
                    break;
                case 1:
                    index += 2;
                    break;
                case 2:
                    index += 3;
                    break;
            }
            int start = prevStart + (count * path) + counter;
            float length = 0;
            if (firstRecursion) length = count;
            else length = count + start;
            int trueIndex = index;
            if (path != 0) trueIndex = index + (path * 3);
            else trueIndex = index;
            for (int i = start; i < length; i += patternLength)
            {
                allColorPaths[i] = allColors[trueIndex];
            }
            if (!firstRecursion) prevStart = start - counter;
            counter++;
            firstRecursion = false;
            RecurseColors(count, 0, counter, index, prevStart, firstRecursion);
            RecurseColors(count, 1, counter, index, prevStart, firstRecursion);
            RecurseColors(count, 2, counter, index, prevStart, firstRecursion);
        }
    }*/
    #endregion
}