# Dice of Doom Game in Elixir, Human Player Only # CSci 556: Multiparadigm Programming, Spring 2015 # H. Conrad Cunningham, Professor # Computer and Information Science # University of Mississippi #234567890123456789012345678901234567890123456789012345678901234567890 # 2015-02-27: Began development following Land of Lisp # 2015-03-01: Prototype eager version with human player only # 2015-03-09: Modified to allow board to be any Dict, but now use Map # instead of HashDict; simplified board_attack defmodule Dice1 do @num_players 2 @max_dice 3 @board_size 2 defp board_hexnum do @board_size * @board_size end # Create a new board, which must be a Dict def new_board() do %{} end # Directly uses Erlang :random module def gen_board() do Enum.into(0..board_hexnum-1, new_board(), fn n -> { n, { :random.uniform(@num_players)-1, :random.uniform(@max_dice) } } end) end def player_letter(n) do String.at("abcdefghijklmnopqrstuvwxyz",n) end def draw_board(board) do IO.puts board_as_string(board) end # board_as_string factored out of draw_board def board_as_string(board) do Enum.map_join(0..@board_size-1, "\n", fn y -> String.duplicate(" ",@board_size-y) <> (Enum.map_join(0..@board_size-1, fn x -> {p,d} = board[x + (@board_size * y)] "#{player_letter(p)}-#{d} " end)) end) end def game_tree(board, player, spare_dice, first_move) do { player, board, add_passing_move(board, player, spare_dice, first_move, attacking_moves(board, player, spare_dice)) } end def add_passing_move(_,_,_,true,moves) do moves # first_move argument true, cannot pass end def add_passing_move(board,player,spare_dice,_,moves) do # first_move argument false, can pass [ { nil, game_tree(add_new_dice(board, player, spare_dice-1), rem(player+1,@num_players), 0, true ) } | moves ] end def attacking_moves(board, cur_player, spare_dice) do Enum.filter_map(board, fn {_,{p,_}} -> p == cur_player end, fn {src,{p0,d0}} -> neighbor_hexes = Enum.map(neighbors(src),&({&1,board[&1]})) Enum.filter_map(neighbor_hexes, fn {_,{p1,d1}} -> p0 != p1 and d0 > d1 end, fn {dst,{_,d1}}-> { {src,dst}, game_tree( board_attack(board,cur_player,src,dst,d0), cur_player, spare_dice + d1, false ) } end ) end) |> Enum.concat() end def neighbors(pos) do up = pos - @board_size down = pos + @board_size up_left = if pos >= @board_size and rem(pos,@board_size) != 0 do [up-1] else [] end up_right = if pos >= @board_size do [up] else [] end left = if rem(pos,@board_size) != 0 do [pos-1] else [] end right = if rem(pos+1,@board_size) != 0 do [pos+1] else [] end down_left = if pos < (board_hexnum - @board_size) do [down] else [] end down_right = if pos < (board_hexnum - @board_size) and rem(pos+1,@board_size) != 0 do [down+1] else [] end up_left ++ up_right ++ left ++ right ++ down_left ++ down_right end def board_attack(board, _, src, dst, dice) do {p0,_} = board[src] board |> Dict.put(src,{p0,1}) |> Dict.put(dst,{p0,dice-1}) end # Exception checking removed # def board_attack(board, player, src, dst, dice) do # {p0,d0} = board[src] # {p1,_} = board[dst] # if p0 != player or d0 != dice do # raise "In board_attack, source hexagon incorrect." <> # "Player must attack from own hexagon." # end # if p1 == player do # raise "In board_attack, destination hexagon incorrect. " <> # "Player cannot attack self." # end # board |> Dict.put(src,{p0,1}) |> Dict.put(dst,{p0,dice-1}) # end def add_new_dice(board,player,spare_dice) do changes = Enum.filter(board, fn {_,{p,d}} -> p == player and d < @max_dice end) |> Enum.take(spare_dice) |> Enum.map(fn {i,{p,d}} -> {i,{p,d+1}} end) |> Enum.into(new_board()) Dict.merge(board,changes) end # The code below is for human players def play_vs_human(tree) do print_info(tree) {_,board,moves} = tree case moves do [] -> announce_winner(board) _ -> play_vs_human(handle_human(tree)) end end def print_info(tree) do {player,board,_} = tree IO.puts "Current player = #{player_letter(player)}" draw_board(board) end def handle_human(tree) do {_,_,moves} = tree menu = Enum.zip(1..board_hexnum,moves) |> Enum.map_join("\n", fn {n, {{src,dst},_}}-> "#{n}: #{src} -> #{dst}" {n, {nil,_}} -> "#{n}: end turn" end) IO.puts menu sel = IO.gets("Choose your move: ") |> String.strip |> String.downcase |> String.split(" ", trim: true) |> List.first |> String.to_integer selmove = if sel < 0 or sel > length(moves) do IO.puts "Invalid choice #{sel}. Forcing choice 1." 0 else sel-1 end Enum.at(moves,selmove) |> elem(1) end def winners(board) do init = List.duplicate(0,@num_players) sums = Enum.reduce(board,init, fn {_,{p,_}}, acc -> List.update_at(acc,p,&(&1+1)) end) best = Enum.max(sums) Enum.zip(0..(@num_players-1),sums) |> Enum.filter_map(&(elem(&1,1) == best),&(elem(&1,0))) end def announce_winner(board) do w = winners(board) if length(w) > 1 do IO.puts "The game is a tie among: " <> Enum.map_join(w,", ", &("#{player_letter(&1)}")) else "The winner is #{player_letter(List.first(w))}" end end # No equivalent in Land of Lisp code def play_game() do board = gen_board tree = game_tree(board,0,0,true) ans = IO.gets("Play against computer? ('y' or 'n') ") |> String.strip |> String.downcase |> String.split(" ", trim: true) |> List.first case ans do "y" -> # play_vs_computer(tree) IO.puts "Play against computer not yet supported." "n" -> play_vs_human(tree) _ -> IO.puts "Invalid input '#{ans}'. Playing against humqn." play_vs_human(tree) end end end