Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Exercise 2b

Exercise-2.livemd

Exercise 2b

Section

defmodule TicTacTo do
  use GenServer

  def start(player1, player2) do
    GenServer.start(__MODULE__, {player1, player2})
  end

  def showState(pid) do
    GenServer.call(pid, {:showState})
  end

  def play(pid, currentPlayer, x, y) do
    GenServer.call(pid, {:makeMove, currentPlayer, x, y})
  end

  def restart(pid, player1, player2) do
    GenServer.cast(pid, {:restart, player1, player2})
  end

  # GenServer callbacks
  def init({player1, player2}) do
    initial_state = %{
      board: ~w(- - - - - - - - -),
      players: [player1, player2],
      current_player: player1,
      turn: 0,
      game_over: false,
      winner: nil
    }

    IO.inspect(initial_state, label: "Current State in handle_call")
    {:ok, initial_state}
  end

  def handle_call({:showState}, _from, state) do
    IO.inspect(state, label: "Current State: ")
    {:reply, state, state}
  end

  def handle_cast({:restart, player1, player2}, state) do
    initial_state = %{
      board: ~w(- - - - - - - - -),
      players: [player1, player2],
      current_player: player1,
      turn: 0,
      game_over: false,
      winner: nil
    }

    IO.inspect(initial_state, label: "Restarting Game")

    {:noreply, initial_state}
  end

  def handle_call({:makeMove, currentPlayer, x, y}, _from, state) do
    index = calculate_index(x, y)

    if state.game_over do
      error_message = "Das Spiel ist bereits beendet. Es kann kein Zug mehr gemacht werden."
      IO.puts("Fehler: #{error_message}")
      {:reply, {:error, error_message}, state}
    else
      case {currentPlayer, state.current_player} do
        {currentPlayer, _} when currentPlayer == state.current_player ->
          case update_board(state, index, currentPlayer) do
            {:ok, updated_state} ->
              case check_winner(updated_state) do
                {:winner, winner_player, winner_state} ->
                  {:reply, {:winner, winner_player}, winner_state}

                _ ->
                  new_state = %{
                    updated_state
                    | turn: updated_state.turn + 1,
                      current_player: next_player(updated_state),
                      game_over: false
                  }

                  if full_board?(new_state.board) do
                    draw_state = %{
                      new_state
                      | game_over: true,
                        winner: "Unentschieden"
                    }

                    IO.puts("Das Spiel endet unentschieden!")
                    {:reply, {:draw, "Unentschieden"}, draw_state}
                  else
                    IO.inspect(new_state, label: "After update_board")
                    {:reply, new_state, new_state}
                  end
              end

            {:error, message} ->
              IO.puts("Fehler: #{message}")
              {:reply, {:error, message}, state}
          end

        {^currentPlayer, _} ->
          error_message =
            "Das ist nicht der Zug von #{currentPlayer}. Es ist der Zug von #{state.current_player}"

          IO.puts("Fehler: #{error_message}")
          {:reply, {:error, error_message}, state}
      end
    end
  end

  defp full_board?(board) do
    Enum.all?(board, fn cell -> cell != "-" end)
  end

  defp calculate_index(x, y) when x in 0..2 and y in 0..2 do
    x + y * 3
  end

  defp calculate_index(_, _) do
    IO.puts("Ungültige Koordinaten. x und y müssen im Bereich von 0 bis 2 liegen.")
    {:error, -1}
  end

  defp next_player(%{players: [player1, player2], current_player: current_player}) do
    if current_player == player1, do: player2, else: player1
  end

  defp update_board(state, index, currentPlayer) do
    case Enum.at(state.board, index) do
      "-" ->
        player_number = player_number(currentPlayer, state)
        new_board = List.replace_at(state.board, index, Integer.to_string(player_number))
        updated_state = %{state | board: new_board}
        {:ok, updated_state}

      _ ->
        IO.inspect("Feld bereits besetzt", label: "Error")
        {:error, "Feld bereits besetzt"}
    end
  end

  defp check_winner(state) do
    lines_to_check = [
      # Horizontale Linien
      {0, 1, 2},
      {3, 4, 5},
      {6, 7, 8},
      # Vertikale Linien
      {0, 3, 6},
      {1, 4, 7},
      {2, 5, 8},
      # Diagonale Linien
      {0, 4, 8},
      {2, 4, 6}
    ]

    winner_state =
      Enum.reduce_while(lines_to_check, {:ok, state}, fn {i, j, k}, acc ->
        if Enum.all?([i, j, k], fn idx ->
             Enum.at(state.board, idx) ==
               Integer.to_string(player_number(state.current_player, state))
           end) do
          new_state = %{
            state
            | winner: state.current_player,
              game_over: true
          }

          IO.puts("#{state.current_player} hat gewonnen!")
          {:halt, {:winner, state.current_player, new_state}}
        else
          {:cont, acc}
        end
      end)

    case winner_state do
      {:ok, _} -> {:ok, state}
      result -> result
    end
  end

  defp player_number(player, state) do
    player_index = Enum.find_index(state.players, &(&1 == player))

    case player_index do
      0 -> 1
      1 -> 2
      _ -> raise "Unknown player: #{player}"
    end
  end

  defp init_state do
    persistent_term_key = :tic_tac_to_state

    case :persistent_term.get(persistent_term_key) do
      {:ok, state} ->
        IO.inspect(state, label: "Restoring State")
        state

      _ ->
        initial_state = %{
          board: ~w(- - - - - - - - -),
          players: [nil, nil],
          current_player: nil,
          turn: 0,
          game_over: false,
          winner: nil
        }

        IO.inspect(initial_state, label: "Initial State")
        :persistent_term.put(persistent_term_key, initial_state)
        initial_state
    end
  end

  defp store_state(state) do
    :persistent_term.put(:tic_tac_to_state, state)
  end
end

defmodule YourApplication do
  use Application

  def start(_type, _args) do
    children = [
      {TicTacToSupervisor, []}
    ]

    opts = [strategy: :one_for_one, name: YourApplication.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
# Spiel starten
{:ok, pid} = TicTacTo.start(:player1, :player2)

# Beispielzüge wo player1 gewinnt
TicTacTo.play(pid, :player1, 0, 0)
TicTacTo.play(pid, :player2, 1, 0)
TicTacTo.play(pid, :player1, 0, 1)
TicTacTo.play(pid, :player2, 1, 1)
TicTacTo.play(pid, :player1, 0, 2)

# Spieler :player2 versucht einen weiteren Zug, sollte einen Fehler geben
TicTacTo.play(pid, :player2, 1, 2)

# Resette das Spiel
TicTacTo.restart(pid, :a, :b)

# Unentschieden
TicTacTo.play(pid, :a, 0, 0)
TicTacTo.play(pid, :b, 1, 0)
TicTacTo.play(pid, :a, 2, 0)
TicTacTo.play(pid, :b, 1, 1)
TicTacTo.play(pid, :a, 0, 1)
TicTacTo.play(pid, :b, 0, 2)
TicTacTo.play(pid, :a, 1, 2)
TicTacTo.play(pid, :b, 2, 1)
TicTacTo.play(pid, :a, 2, 2)