Changes I might make, in no particular order:
Things that are not named the same, that could be:
GenPlayer.buy_chips, Genplayer handles message :add_chips
GenPlayer.deal_to, vs :deal_cards
For messages that only require an atom, (:name, :stack, :cards), I would just send the atom, rather than wrapping it in a tuple
For the handle_calls that look into the state, such as handle_call({:stack}..) consider pattern matching to pull out the piece you're after:
def handle_call(:stack, _from, %{stack: stack} = pd), do: {:reply, stack, pd}
One other thing. This may not be idiomatic, it might easily be something I just made up, but I really like having little state mutators, that take a state as the first param, and return a state, like this:
def add_chips(s = %{stack: chips}, amount), do: s |> Map.put(:stack, chips + amount)
def reply(state, return), do: {:reply, return, state}
def noreply(state), do: {:noreply, state}
def handle_cast({:add_chips, amount}, state), do: state |> add_chips(amount) |> noreply