Error: undefined method '[]' for Int32

I am trying to get an item from (what I think to be) a hash with [](key) but instead of getting the value I get this undefined [] for Int32 error instead:

Error: undefined method '[]' for Int32 (compile-time type is (Hash(String, Array(Hash(String, Array(NamedTuple(x: Int32, y: Int32)) | Int32 | NamedTuple(x: Int32, y: Int32) | String)) | Array(NamedTuple(x: Int32, y: Int32)) | Int32) | Hash(String, Array(NamedTuple(x: Int32, y: Int32)) | Int32 | NamedTuple(x: Int32, y: Int32) | String) | Hash(String, Hash(String, String) | Int32 | String) | Int32))

I have the following aliases and methods to generate snakes and game states

aliases:

alias Coords = NamedTuple(x: Int32, y: Int32)
alias Snake = Hash(String, Array(Coords) | Int32 | Coords | String)
alias GameState = Hash(String, Hash(String, String | Hash(String, String) | Int32) | Int32 | Hash(String, Int32 | Array(Coords) | Array(Snake)) | Snake)

methods:

def create_game_state(snake : Battlesnake::Snake) : Battlesnake::GameState
    {
        "game" => {
            "id" => "",
            "ruleset" => {"name" => "", "version" => ""},
            "timeout" => 0
        },
        "turn" => 0,
        "board" => {
            "height" => 0,
            "width" => 0,
            "food" => [] of Battlesnake::Coords,
            "snakes" => [snake],
            "hazards" => [] of Battlesnake::Coords
        },
        "you" => snake
    }
end

def create_battlesnake(id : String, body_coords : Array(Battlesnake::Coords)) : Battlesnake::Snake
    {
        "id" => id,
        "name" => id,
        "health" => 0,
        "body" => body_coords,
        "latency" => "",
        "head" => body_coords[0],
        "length" => body_coords.size,
        "shout" => "",
        "squad" => ""
    }
end

Example:

snake = create_battlesnake("me", [{ x: 2, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 0 }])
game_state = create_game_state(snake)

move_response = Battlesnake.move(game_state) # Battlesnake is the module name

in the def move(game_state : GameState) : Hash(String, String) method when I output game_state with pp I get

{"game" =>
  {"id" => "", "ruleset" => {"name" => "", "version" => ""}, "timeout" => 0},
 "turn" => 0,
 "board" =>
  {"height" => 0,
   "width" => 0,
   "food" => [],
   "snakes" =>
    [{"id" => "me",
      "name" => "me",
      "health" => 0,
      "body" => [{x: 2, y: 0}, {x: 1, y: 0}, {x: 0, y: 0}],
      "latency" => "",
      "head" => {x: 2, y: 0},
      "length" => 3,
      "shout" => "",
      "squad" => ""}],
   "hazards" => []},
 "you" =>
  {"id" => "me",
   "name" => "me",
   "health" => 0,
   "body" => [{x: 2, y: 0}, {x: 1, y: 0}, {x: 0, y: 0}],
   "latency" => "",
   "head" => {x: 2, y: 0},
   "length" => 3,
   "shout" => "",
   "squad" => ""}}

and pp game_state["you"] outputs

{"id" => "me",
 "name" => "me",
 "health" => 0,
 "body" => [{x: 2, y: 0}, {x: 1, y: 0}, {x: 0, y: 0}],
 "latency" => "",
 "head" => {x: 2, y: 0},
 "length" => 3,
 "shout" => "",
 "squad" => ""}

These two outputs are what I would expect, however, when I try pp game_state["you"]["head"] I get the undefined [] for Int32` mentioned at the top.

Am I missing something or maybe I have mis-understood something about hashes?

When I try to check the type of each value in game_state

game_state.each do |k, v|
  puts "#{k} => #{typeof(v)}\n"
end

I get the same value for each item in the hash

game => (Hash(String, Array(Hash(String, Array(NamedTuple(x: Int32, y: Int32)) | Int32 | NamedTuple(x: Int32, y: Int32) | String)) | Array(NamedTuple(x: Int32, y: Int32)) | Int32) | Hash(String, Array(NamedTuple(x: Int32, y: Int32)) | Int32 | NamedTuple(x: Int32, y: Int32) | String) | Hash(String, Hash(String, String) | Int32 | String) | Int32)
turn => (Hash(String, Array(Hash(String, Array(NamedTuple(x: Int32, y: Int32)) | Int32 | NamedTuple(x: Int32, y: Int32) | String)) | Array(NamedTuple(x: Int32, y: Int32)) | Int32) | Hash(String, Array(NamedTuple(x: Int32, y: Int32)) | Int32 | NamedTuple(x: Int32, y: Int32) | String) | Hash(String, Hash(String, String) | Int32 | String) | Int32)
board => (Hash(String, Array(Hash(String, Array(NamedTuple(x: Int32, y: Int32)) | Int32 | NamedTuple(x: Int32, y: Int32) | String)) | Array(NamedTuple(x: Int32, y: Int32)) | Int32) | Hash(String, Array(NamedTuple(x: Int32, y: Int32)) | Int32 | NamedTuple(x: Int32, y: Int32) | String) | Hash(String, Hash(String, String) | Int32 | String) | Int32)
you => (Hash(String, Array(Hash(String, Array(NamedTuple(x: Int32, y: Int32)) | Int32 | NamedTuple(x: Int32, y: Int32) | String)) | Array(NamedTuple(x: Int32, y: Int32)) | Int32) | Hash(String, Array(NamedTuple(x: Int32, y: Int32)) | Int32 | NamedTuple(x: Int32, y: Int32) | String) | Hash(String, Hash(String, String) | Int32 | String) | Int32)

I have no idea why I get this error and I have been staring at it for past hour or so and I can’t really figure out why I get this error.

I hope this makes sense otherwise please let me know and I will try to clarify.

I think it all boils down to the type of the value of your GameState hash is like Hash(String, String | Hash(String, String) | Int32) | Int32 ... I.e. when you do game_state["you"] it’s possible that returns either a Hash or Int32. The compiler can’t prove that it wouldn’t return an Int32, so it errors because the #[] method doesn’t exist on all of the possible types in the union.

I’d suggest not using hashes like this as they’re a real pain to work with, especially this deeply nested. But to fix this, you first need to ensure (or tell the compiler) the the value of game_state["you"] is in fact a Hash.

3 Likes

I see. I will try to find a way to get rid of the nested hashes - thank you so much for your help!

I use structs for my Battlesnake like this

struct State
  include JSON::Serializable

  getter game : Game
  getter turn : Int32
  getter board : Board
  getter you : Battlesnake
end

struct Game
  include JSON::Serializable

  getter id : String
  getter ruleset : Ruleset
  getter timeout : Int32
  getter source : String
end

It makes the entire thing nicer to work with.

You can then parse the json from Battlesnake with State.from_json or add an initialize method for use in tests or elsewhere.

2 Likes