Skip to content

BONUS! Fishing Mastery⚓︎

Difficulty:

Objective⚓︎

Request

Catch at least one of each species of fish that live around Geese islands. When you're done, report your findings to Poinsettia McMittens.

Poinsettia McMittens

Now, just imagine if we had an automatic fish catcher? It would be as ingenious as me on a good day!

I came across this fascinating article about such a device in a magazine during one of my more glamorous fishing sessions.

If only I could get my hands on it, I'd be the undisputed queen of catching them all!

Hints⚓︎

I Am Become Data

From: Poinsettia McMittens

One approach to automating web tasks entails the browser's developer console. Browsers' console allow us to manipulate objects, inspect code, and even interact with websockets.

Fishing Machine

From: Poinsettia McMittens

There are a variety of strategies for automating repetative website tasks. Tools such as AutoKey and AutoIt allow you to programmatically examine elements on the screen and emulate user inputs.

Solution⚓︎

Manally catching all 171 species may be possible, but is a task clearly in need of automation.

Game Mechanics⚓︎

The browser's developer console shows a web socket connectin to wss://2023.holidayhackchallenge.com/sail?dockSlip=... while we are sailing and fishing. Several message types on this web socket are relating to our fishing activity:

  • "cast": The client sends this when we cast a line ...
  • "reel": ... and this when we reel it in
  • ":f{some json}": This come from the server when we have caught a fish. The JSON part describes the fish.
  • ":e{some json}": This seems to denote some event, including when a fish "bites". A description of the JSON part follows:

json part of e: message
    {
      "11302": {
        "uid": 11302,
        "username": "triback",
        "x": 545.4443170240962,
        "y": 935.7503830535603,
        "o": -1,
        "vx": 0,
        "vy": 0,
        "config": { "colors": [ "yellow", "blue", "blue" ],
          "progress": [ true, true, true, true, true, true ]
        },
        "fishCaught": [
          {
            "name": "Sushinano Sweetsquid",
            "description": "\n- The Sushinano Sweetsquid is an endearing and vibrant fish with an oversized head, resembling a rainbow-colored squid fused with a candy-striped stick of rock.\n- It sports a pair of playful appendages that resemble pineapple slices, aiding its buoyancy just as pufferfish use their scales.\n- Also adorning the sides of this delightful creature are feather-like fins, their iridescence reminiscent of peacock feathers and casting an enchanting glow in the ocean depths.\n- The tail of the Sushinano Sweetsquid is formed peculiarly, like a maple leaf and flashes alternating hues that mimic a neon sign, making this fish an eye-catching spectacle in its marine habitat.\n- It has bulbous googly-eyes evident of tree frog influences, giving it an adorably expressive countenance.\n- In conjunction with its fish characteristics, this unique creature borrows its miniature trumpet-like snout from the elephant, useful for sniffing out its favorite meals in dark ocean crevices.\n- The non-fish-like and inanimate attribute of the Sushinano Sweetsquid is its popcorn kernel-shaped and textured scales, which provide a protective armor while also giving the fish a delightful, whimsical appearance.",
            "hash": "53d5545920b15f6f9d26aea7b8b68070"
          },
          {
            "name": "Fantasia Fluffernutter Finfish",
            "description": "A vibrant, luminescent fish equipped with butterfly wing-like dorsal fins that enable it to mildly hover above water. Its body is covered in plush, teddy bear-like fur instead of the typical fish scales. Its snout is long and thin like an anteater, perfect for reaching into small crannies. An extraordinary feature is its high hat made up entirely of barnacle shells, which the fish uses for both protection and a show of ranks amongst its species. It has small, yet expressive, broccoli stalk-shaped antennae that quiver with emotion.",
            "hash": "a7068c3f505f77adf1c48ed85a469062"
          },
          ...
        ] ,
        "bearing": null,
        "canFish": true,
        "ports": [
          "pi-driftbitgrotto",
          ...
          "ci-rudolphsrest"
        ],
        "showOthers": false,
        "keyState": 0,
        "colors": [ "yellow", "blue", "blue" ],
        "progress": [ true, true, true, true, true, true ],
        "fishing": true,
        "onTheLine": "Bumblecado Finstache Hybridsail",
        "c": 0
      }
    }
We see our username, our coordinates and speed, the colors of our sails, ports we can visit (or have visited?) and some more.

"fishCaught" holds a tally of all the species we already reeled in.

But most important, the key "onTheLine" shows whether the fishing line is free (it will show a "false" value), or if a fish currently bites. Like in the sample, it will contain the name of the species.

The sequence of events for the "fishing game" now becomes clear:

  • We cast the line, a "cast" message is sent to the server
  • The server replies with a stream of "e:" messages, with "onTheLine" set to "false"
  • After some random time, the "e:" message will have "onTheLine" set to a fish species
  • The client sets the "Reel"-button to red
  • When we press the "Reel" button now, a "reel" message is send
  • and the server replies with an "f:" message detailing what fish we caught. In the background, our score table registers the catch if it was a new species.
  • Should we press the "Reel" button too early or too late, a "reel" message is sent, but no "f:" confirms a catch.

The question now is: How can we automate the fishing?

How to Fish⚓︎

The javascript source code for the client part intrigues:

...
const websockHost = window.SEA_WS_HOST !== 'UNSET' ? window.SEA_WS_HOST : (window.SEA_WS_HOST === 'UNSET' ? 'ws://localhost:3000/sail' : '');
const socket = new WebSocket(`${websockHost}?dockSlip=${UrlParams.dockSlip}`);
...
socket.addEventListener("message", event => {
  const messageType = event.data.substr(0, 2);
  const payload = event.data.substr(2);
  if (messageType === 'e:') {
   ...
It shows that the variable "socket" holds the web socket connection, and further down, how code can register for events on that socket.

We craft a javascript codelet based on these building blocks and paste it into the browser's javascript console

Context

The javascript console of Chromium has a small context selector in the top left corner. We must make sure that this is set to "sea/", not to "top" when pasting.

automatically reel in new species of fish
socket.addEventListener("message", event => {
  const messageType = event.data.substr(0, 2);
  const payload = event.data.substr(2);
  if (messageType === 'e:') {
    const parsed = JSON.parse(payload);
    if (parsed["11302"]["onTheLine"]) {
        let fish=parsed["11302"]["onTheLine"]
        for (caught of parsed["11302"]["fishCaught"]) {
        if (caught["name"] == fish) {
            console.log("Already caught: "+fish)
                    return;
        }
    }
        console.log("Catching "+fish);
    socket.send("reel");
        setTimeout( function() { socket.send("cast"); }, 500 );
    }
  }
})
Note our own user id "11302" in line 6. This must be replaced with the id of the current user, visible e.g. in "e:"-type messages.

The code adds another event listener on "socket". This event listener waits for an e:-message indicating that a fish is on the line. But it does not try to catch the fish at once. Instead, it compares its species to the list of already caught ones conveniently included as "fishCaught" in the "e:" message.

Only a new species is reeled in by sending a "reel" message on the web socket.

After a pause of 500 milliseconds, it send "cast" on the socket to cast the line again.

I cannot know whether only catching new species and ignoring others increases our odds - but it seems the more proper way.

We start that code and watch our Pescadex growing.

Where to Fish⚓︎

At the outset, it does not matter where we fish - we will capture many new species everywhere. Moving somewhere else after some time should increase our quota of new species.

When only few species are uncaught, we must refine tactics.

https://2023.holidayhackchallenge.com/sea/fishdensityref.html from the "Fishing Guide" challenge gives us heat maps of fish locations.

This python program will read all heatmaps in direcory "uncaught", sum up the white values and output a new map showing where we stand a good chance of catching a new species. For the ease of navigation, it will also overlay the map of Geese Islands.

Python program to create heatmap overlays
overlay.py
#!/usr/bin/python3

import sys
import os
import os.path
import png
import json
import copy


def minvalue (rows):
    minv=rows[0][0]
    for y in range(0,250):
        for x in range(0,250):
            if rows[y][x]<minv: minv=rows[y][x]
    return minv

def maxvalue (rows):
    maxv=0
    for y in range(0,250):
        for x in range(0,250):
            if rows[y][x]>maxv: maxv=rows[y][x]
    return maxv



# returns rows as 0..255
def get_rows(filename):
    print(f"get_rows(): reading {filename}")
    image=png.Reader(filename)
    (width,height,rgba_rows,info)=image.asRGBA8()
    if  width != 250 or height != 250: 
        print(f"{filename}: not 250x250")
        sys.exit()

    rgba_rows=list(rgba_rows)

    rows=  [[0 for col in range(250)] for row in range(250)]
    for y in range(0,250):
        for x in range(0,250):
            rows[y][x]=rgba_rows[y][x*4]

    return rows



def write_png(rows, filename):
    # expand or compres scale so the range 0..255 is well used

    # to int, and scale to 0..255
    max_value=maxvalue(rows)
    min_value=minvalue(rows)
    delta_value=max_value-min_value

    # png_rows will hold the pixel data: 4 bytes 0..255 per pixel (R,G,B,A) x 250 pixel x 250 rows
    png_rows=  [[0 for col in range(250*4)] for row in range(250)]
    for y in range(0,250):
        for x in range(0,250):
            v8=128
            if delta_value>0: v8=int( ( rows[y][x]-min_value) / delta_value * 255)

            if v8<0: v8=0
            if v8>255: v8=255

            png_rows[y][x*4]=v8
            png_rows[y][x*4+1]=v8
            png_rows[y][x*4+2]=v8
            png_rows[y][x*4+3]=255  # alpha-channel

    # overlay minimap.png
    image=png.Reader(filename="minimap.png")
    (width,height,minimap_rows,info)=image.asRGBA8()
    minimap_rows=list(minimap_rows)
    for y in range(0,250):
        for x in range(0,250):
            if minimap_rows[y][x*4+3] >128:
                png_rows[y][x*4  ]=10
                png_rows[y][x*4+1]=50
                png_rows[y][x*4+2]=10

    png.from_array(png_rows, 'RGBA').save(filename)


# directory where we store the heatmaps of yet uncaught species
uncaught=os.listdir("uncaught")

result=  [[0 for col in range(250)] for row in range(250)]

for fish in uncaught:
    print(f"fish: {fish}")

    fish_rows=get_rows("uncaught/"+fish)
    for y in range(0,250):
        for x in range(0,250):
            result[y][x]+=fish_rows[y][x]

write_png(result, f"overlay_uncaught-{len(uncaught)}.png")

With four species remaining, this was the resulting map:

overlay map

Fishing near the "feet" of Steampunk Island was successful.

Special Case: Piscis Cyberneticus Skodo⚓︎

Even the heat map is special:

heatmap

Just a small spot on the throat of Steampunk Island.

Catching Skodo took almost an hour of automatic fishing - time well worth spending:

skodo

Caught 171 species of amazon, colorful "fish

Many thanks for creating this bonus challenge. Watching the fish all the time was a great pleasure, as was reading the Pescadex descriptions.

So long, and thanks for all the fish!

Images⚓︎

Answer

After solving the challenge, the fact will be listed as an "Achievements" in the player's badge.

Response⚓︎

Poinsettia McMittens

You managed to catch every fish? You're like the fishing version of a Christmas miracle!

Now, if only you could teach me your ways... but then again, I'm already pretty fabulous at everything I do.