ndac_todoroki

ndac_todoroki

Thinking of implementing a Chess game in Elixir, I created a module which will represent a chess piece as a struct.

defmodule Piece do
  alias __MODULE__, as: Piece
  @piece_types ~w(King Queen Rook Bishop Knight Pawn)a
  @type type :: :King | :Queen | :Rook | :Bishop | :Knight | :Pawn

  defstruct [:type]
  @type t :: %Piece{ type: type() }

  defguard is_type(piece_type) when piece_type in @piece_types
      
  def new(type) when is_type(type), do: %Piece{type: type}
end

then there is a struct that represents an active piece:

defmodule Fighter do
  alias __MODULE__, as: Fighter
  @type owner ::  :player | :opponent
  
  defstruct [ :owner, :piece, :position ]
  @type t :: %Fighter{ owner: owner(), piece: Piece.t(), position: BoardCoordinate.t }
end

So I have a List of Fighters when the game starts. I came to a circumstance where I want to sort the Fighters.
The result should be sorted first by fighter.owner, then by fighter.piece.type, and then by fighter.position, so it will be

  • my king
  • my queen
  • my most left-top pawn
  • my secondly left-top pawn
  • opponent’s king
  • opponent’s queen

and so on. Can I perform this on a single custom sort? (Like performing fighters |> Fighter.sort())?
For that I thought setting a default sort algorism for each struct type could be a simple way, but I couldn’t have sorted(!) this out.

Any help and thoughts are welcomed!

Showing Posts 1 to 10

kokolegorille

kokolegorille

Not that it helps with your sorting question, but I have done two chess libs in Elixir…

The second is a translated from Erlang and generates all possible moves. Maybe it helps

japplegame

japplegame

My AVLTree implementation with custom sorting function: GitHub - japplegame/avl_tree: Pure Elixir AVL tree implementation · GitHub

ndac_todoroki

ndac_todoroki OP

Thanks, but this is just an example for a more generic question… Sorting your nested structs in ways you desire to.
But I’m gonna have fun with those repos! :+1:

ndac_todoroki

ndac_todoroki OP

Giving a custom function to (e.g. Enum.sort/3) was what I first considered, but it doesn’t help when you want to sort nested structs… :disappointed:
But anyway, I like your AVLTree! It is very simple. Thanks!

ndac_todoroki

ndac_todoroki OP

And I’ve kind of came up with some way: implementing a custom sort Protocol may do.

defprotocol ChessSorter do
  @spec asc(any, any) :: boolean
  def asc(_, _)
end
defmodule Coordinate do
  defstruct [:x, :y]

  defimpl ChessSorter, for: __MODULE__ do
    def asc(%{x: x1, y: y1}, %{x: x2, y: y2}) when x1 == x2, do: y1 <= y2
    def asc(%{x: x1}, %{x: x2}), x1 <= x2
  end
end
defmodule Piece do
  defstruct [:type]

  defimpl ChessSorter, for: __MODULE__ do
    @types %{
      Pawn => 1,
      Knight => 2,
      Bishop => 3,
      Rook => 4,
      Queen => 5,
      King => 6,
    }

    def asc(%{type: t1}, %{tpe: t2}), do: @types[t1] <= @types[t2]
  end
end
defmodule Fighter
  defstruct [ :owner, :piece, :position ]

  defimpl ChessSorter, for: __MODULE__ do
    def asc(
          %{owner: o1, piece: p2, position: pos1},
          %{owner: o2, piece: p2, position: pos2}
        )
        when o1 == o2 and p1 == p2,
        do: ChessSorter.asc(pos1, pos2)

    def asc(%{owner: o1, piece: p2}, %{owner: o2, piece: p2}) when o1 == o2,
      do: ChessSorter.asc(p1, p2)

    def asc(%{owner: :opponent}, %{owner: :player}), do: false
    def asc(%{owner: :player}, %{owner: :opponent}), do: true
  end
end

And then : fighters |> Enum.sort(&ChessSorter.asc/2). So this recursively does sorting down to basic Kernel.<= s.

But I am afraid this may be veery slow. :disappointed: Is it?

Qqwy

Qqwy

TypeCheck Core Team

Elixir’s sorting function is stable. This means that you can first sort the whole list of fighters on the least-important condition, then on the second-least important condition , etc. and finally on the most important condtion, and they will end up in the order you’d expect.

So in your case

fighters
|> Enum.sort_by(fn fighter -> fighter.position end)
|> Enum.sort_by(fn fighter -> fighter.piece.type end)
|> Enum.sort_by(fn fighter -> fighter.owner end)

And, by taking advantage of the fact that tuples are ordered elementwise (we look at the next element only if the current element is equal), we could write it like this instead as well:

fighters
|> Enum.sort_by(fn fighter -> {fighter.owner, fighter.piece.type, fighter.position} end)

However, this will not give the answer you’d like yet, because many things are represented by symbols, which by default are sorted alphabetically (whereas you’d like the given order King > Queen > Bishop > ... and player > opponent. The translation functions you wrote for your custom sorter would indeed be a solution to this.

It is possible to automate that a little, by creating a map of the numerically ordered equivalents (like piece_type_ordering = piece_types |> Enum.with_index |> Enum.into(%{})), which you’d want to generate at compile time, so you can just write a function then that will turn a given thing into their ordering.

I think a protocol like the following might make sense:

defprotocol Orderable
  @doc """ 
   Turns the given structure into something that can be compared and sorted in the desired order.
   """
  @spec orderable(any) :: any
  orderable(_)
end

whose implementation for e.g. %Piece would simply return the number that is stored for the given type’s symbol key.

And for %Fighter, it would return e.g.

def orderable(fighter)
  import Orderable
  {orderable(fighter.owner), orderable(fighter.piece), fighter.position}
end
ndac_todoroki

ndac_todoroki OP

Wow, this is amazing how minimum the code could be. It was a whole new study for me. Thanks for the information!!

japplegame

japplegame

With Orderable protocol you also can use AVLTree like this:

AVLTree.new(fn a, b -> Orderable.orderable(a) < Orderable.orderable(b))

All inserted elements will be automatically sorted.

Qqwy

Qqwy

TypeCheck Core Team

@japplegame Interesting! I wrote a library a while back to build and work with Priority Queues called prioqueue, which abstracts away the implementation details of how this queue is maintained, and lets you plug in your own. I think making an AVLTree implementation would be a great idea! When will you upload AVLTree to Hex.PM? :slight_smile:


@ndac_todoroki Glad to be able to help. I guess the Orderable protocol could be wrapped in a library, since I think it is rather common to want to do this kind of stuff. We might even provide built-in implementations for common ordered enumerables that call Orderable.orderable for all their contained elements… Let me conjure up something! :smiley:

Qqwy

Qqwy

TypeCheck Core Team

Done! The orderable library has been built, tested, documented and published.

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
subsaharancoder
I’ve followed the Phoenix LiveView file upload code here Uploads — Phoenix LiveView v1.0.0-rc.7 and so far everything works just fine wit...
New
mohsen
I’m using an Umbrella project for a Phoenix application, and I want to have one Ecto Repo and one PostgreSQL database shared by all apps....
New
jaybe78
Hello, I’m developing a online persistent chat system (what’s app) like using elixir/dynamodb/aws for a mobile app(flutter). The diffic...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews