Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ erl_crash.dump
*.lock
rel
*.swp
.elixir_ls
29 changes: 29 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "mix_task",
"name": "mix (Default task)",
"request": "launch",
"projectDir": "${workspaceRoot}"
},
{
"type": "mix_task",
"name": "mix test",
"request": "launch",
"task": "test",
"taskArgs": [
"--trace"
],
"startApps": true,
"projectDir": "${workspaceRoot}",
"requireFiles": [
"test/**/test_helper.exs",
"test/**/*_test.exs"
]
}
]
}
18 changes: 9 additions & 9 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ config :servus,

# Configuration for a connect-four game
config :servus,
connect_four: %{
adapters: [
tcp: 3334,
web: 3335
],
players_per_game: 2,
implementation: ConnectFour
}
connect_four: %{
adapters: [
tcp: 3334,
web: 3335
],
players_per_game: 2,
implementation: ConnectFour
}

import_config "#{Mix.env}.exs"
import_config "#{Mix.env()}.exs"
2 changes: 1 addition & 1 deletion config/test.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use Mix.Config

config :logger,
level: :error
level: :debug
53 changes: 31 additions & 22 deletions lib/connect_four/connectfour.ex
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
defmodule ConnectFour do
use Servus.Game
require Logger
require IO
use Servus.Game

alias Servus.Serverutils

def init(players) do
Logger.debug "Initializing game state machine"
Logger.debug("Initializing game state machine")

[player2, player1] = players
{:ok, field_pid} = Gamefield.start_link
{:ok, field_pid} = Gamefield.start_link()

fsm_state = %{player1: player1, player2: player2, field: field_pid}

Expand All @@ -25,11 +26,13 @@ defmodule ConnectFour do
decide what to do.
"""
def abort(player, state) do
Logger.warn "Player #{player.name} has aborted the game"
Logger.warn("Player #{player.name} has aborted the game")

cond do
player.id == state.player1.id ->
Serverutils.send(state.player2.socket, "abort", state.player1.id)
{:stop, :shutdown, state}

player.id == state.player2.id ->
Serverutils.send(state.player1.socket, "abort", state.player2.id)
{:stop, :shutdown, state}
Expand All @@ -40,14 +43,16 @@ defmodule ConnectFour do
FSM is in state `p2`. Player 2 puts.
Outcome: p1 state
"""
def p2({id, "put", slot}, state) do
def handle_event(:cast, {id, "put", slot}, :p2, state) do
cond do
id != state.player2.id ->
Logger.warn "Not your turn"
Logger.warn("Not your turn")
{:next_state, :p2, state}

slot in 0..7 ->
Gamefield.update_field(state.field, slot, :p2)
if Gamefield.check_win_condition(state.field) do

if Gamefield.check_win_condition(state.field) do
# Send the final move to the loosingn player
Serverutils.send(state.player1.socket, "set", slot)

Expand All @@ -56,37 +61,40 @@ defmodule ConnectFour do
Serverutils.send(state.player1.socket, "loose", nil)
{:next_state, :win, state}
else
#No win yet
# No win yet
# Notify the other player about the put...
Serverutils.send(state.player1.socket, "set", slot)

# ...and give him the next turn.
Serverutils.send(state.player1.socket, "turn", nil)
{:next_state, :p1, state}
end

true ->
Logger.warn "Invalid slot: #{slot}"
Logger.warn("Invalid slot: #{slot}")
{:next_state, :p2, state}
end
end

def p2({_, "restart", _}, state) do
Logger.warn "Restart not allowed while game is ongoing"
def handle_event(:cast, {_, "restart", _}, :p2, state) do
Logger.warn("Restart not allowed while game is ongoing")
{:next_state, :p2, state}
end

@doc """
FSM is in state `p1`. Player 1 puts.
Outcome: p2 state
"""
def p1({id, "put", slot}, state) do
def handle_event(:cast, {id, "put", slot}, :p1, state) do
cond do
id != state.player1.id ->
Logger.warn "Not your turn"
Logger.warn("Not your turn")
{:next_state, :p1, state}

slot in 0..7 ->
Gamefield.update_field(state.field, slot, :p1)
if Gamefield.check_win_condition(state.field) do

if Gamefield.check_win_condition(state.field) do
# Set the final move to the loosing player
Serverutils.send(state.player2.socket, "set", slot)

Expand All @@ -95,26 +103,27 @@ defmodule ConnectFour do
Serverutils.send(state.player2.socket, "loose", nil)
{:next_state, :win, state}
else
#No win yet
# No win yet
# Notify the other player about the put...
Serverutils.send(state.player2.socket, "set", slot)

# ...and give him the next turn.
Serverutils.send(state.player2.socket, "turn", nil)
{:next_state, :p2, state}
end

true ->
Logger.warn "Invalid slot: #{slot}"
Logger.warn("Invalid slot: #{slot}")
{:next_state, :p1, state}
end
end

def p1({_, "restart", _}, state) do
Logger.warn "Restart not allowed while game is ongoing"
{:next_state, :p2, state}
def handle_event(:cast, {_, "restart", _}, :p1, state) do
Logger.warn("Restart not allowed while game is ongoing")
{:next_state, :p1, state}
end

def win({_, "restart", _}, state) do
def handle_event(:cast, {_, "restart", _}, :win, state) do
Gamefield.reset_game(state.field)
Serverutils.send(state.player1.socket, "reset", nil)
Serverutils.send(state.player2.socket, "reset", nil)
Expand Down
49 changes: 28 additions & 21 deletions lib/connect_four/gamefield.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ defmodule Gamefield do
GenServer.start_link(__MODULE__, produce_empty_field())
end

def init(init_arg) do
{:ok, init_arg}
end

@doc """
Reset the game field to empty state
"""
Expand All @@ -21,8 +25,8 @@ defmodule Gamefield do
@doc """
Put a coin of `playerID` into `slot`
"""
def update_field(server,slot,playerID) do
GenServer.call(server, {:updateField,slot, playerID})
def update_field(server, slot, playerID) do
GenServer.call(server, {:updateField, slot, playerID})
end

@doc """
Expand All @@ -38,56 +42,59 @@ defmodule Gamefield do
# eight arrays. Each of the nested arrays contains eight times the
# value `nil` (empty state).
defp produce_empty_field do
for _ <- 0..7, do: (for _ <- 0..7, do: nil)
for _ <- 0..7, do: for(_ <- 0..7, do: nil)
end

# Implementation

def handle_cast(:resetGame, _) do
{:noreply, produce_empty_field}
{:noreply, produce_empty_field()}
end

def handle_call({:updateField, slot,playerID}, _, gamefield)do
def handle_call({:updateField, slot, playerID}, _, gamefield) do
column = Enum.at(gamefield, slot)
newColumn = put_coin(column, playerID, 0)
newGameField = Listhelp.put_item(gamefield, slot, newColumn)
{:reply, :ok, newGameField}
end

def handle_call(:checkWinCondition,_,gamefield) do
def handle_call(:checkWinCondition, _, gamefield) do
values = [
gamefield,
Fieldchecker.rotate(gamefield),
Fieldchecker.arrow_right(gamefield),
Fieldchecker.arrow_left(gamefield)
]

status = Enum.reduce values, false, fn (x, acc) ->
acc or check_field(x)
end
status =
Enum.reduce(values, false, fn x, acc ->
acc or check_field(x)
end)

{:reply, status, gamefield}
end


# Check the game field. If one of the columns contains
# a winning condition (four coins of the same type in
# a winning condition (four coins of the same type in
# a row) then return true.
defp check_field(field) do
values = Enum.map(field, fn(column) ->
case Fieldchecker.count(column) do
{_, 4} ->
true
_ ->
false
end
end)

Enum.reduce(values, false, fn (x, acc) -> x or acc end)
values =
Enum.map(field, fn column ->
case Fieldchecker.count(column) do
{_, 4} ->
true

_ ->
false
end
end)

Enum.reduce(values, false, fn x, acc -> x or acc end)
end

# Put a coin of `player` in `column`
defp put_coin(column, _, level) when level > 7, do: column

defp put_coin(column, player, level) do
if Enum.at(column, level) == nil do
Listhelp.put_item(column, level, player)
Expand Down
Loading