%%%------------------------------------------------------------------- %%% @author Daniel latham %%% @copyright (C) 2016, Daniel Latham %%% @doc %%% Implement a two client version of a text based tic-tac-toe program. To this end develop a module t3 %%% that contains the following functions: %%% %%% – newgame() starts a new game node and waits for an opponent. %%% %%% – playwith(Opponent) connects to another Erlang node identified by Opponent and starts a new game. %%% If the Opponent does not exist, a call to playwith results in an error. When the game starts the two clients %%% randomly decide who starts the game. %%% %%% – placetoken(Coordinate) validates that Coordinate is a valid move and places a new token at field Coordinate. %%% The move is also communicated to the opponent’s client. Coordinate is coordinate in the form of a1, . . ., c3. %%% When a winning position is reached or no more move is possible, the program announces the result on both clients %%% and the game terminates. If it is not a client’s turn, a call to placetoken prints an error message. %%% If there is no ongoing game, a call to placetoken may result in an error. %%% %%% – tell(Message) sends a message to the opponent. The message will be printed on the console. %%% %%% Hint: This site may give you some ideas: http://ninenines.eu/articles/tictactoe/ %%% Note: If the problem description is unclear, please use the Canvas forum to seek clarification. %%% (20 points – extra credit) Extend the two client version so the game can be played in a distributed environment. %%% %%%TORUN: %%% Open two terminals and connect to moat.cis.uab.edu %%% You should see two vulcan machines %%% Go to the directory where this file is saved %%% Type below where YOURNAME and OTHERNAME are consistent %%% 1$ erl -sname YOURNAME -setcookie .erlang.cookie %%% 2$ erl -sname OTHERNAME -setcookie .erlang.cookie %%% In both type the following %%% (YOURNAME@vulcanX)> c(t3). %%% (OTHERNAME@vulcanY)> c(t3). %%% Next, start the game with player one(can be you or other, using you) %%% (YOURNAME@vulcanX)> t3:newgame(). %%% Connect to game from player two %%% (OTHERNAME@vulcanY)> t3:playwith(YOURNAME@vulcanX). %%% %%% @end %%% Created : 16. Apr 2016 2:20 PM %%%------------------------------------------------------------------- -module(t3_v2). -author("Daniel Latham"). %% API -export([newgame/0, playwith/1, tell/1, client/0, data/1, placetoken/1]). %%% Starts new game for player %%% Use this before trying to connect newgame() -> io:fwrite("Newgame created for Player~n"), My_Game_PID = spawn(t3_v2, client, []), % Register name of client so that other client can communicate with this process % on this node register(player, My_Game_PID), My_Game_PID. %%% Use this to play with another node on the same or other machine on the same network %%% Usage: t3:playwith(bob@address). playwith(Other_Game_Node)-> Response = net_adm:ping(Other_Game_Node), % Check that response is not a 'pang' if Response == pang -> io:fwrite("Invalid host or username. It is possible that the host or user is "), io:fwrite("offline or your secret cookie is incorrectly setup. Please try again.~n"); true -> My_Game_PID = newgame(), {player, Other_Game_Node} ! {connect, My_Game_PID} end. %%% Sends message to other player's node tell(Message) -> Other = get_player(), Other ! {message, Message}. %%% Generates client for saving data(board, other player, etc) data(OtherPlayer, Board, Piece, Lock)-> receive {get_player, Node} -> Node ! {give_player, OtherPlayer}, data(OtherPlayer, Board, Piece, Lock); {get_board, Node} -> Node ! {give_board, Board}, data(OtherPlayer, Board, Piece, Lock); {get_piece, Node} -> Node ! {give_piece, Piece}, data(OtherPlayer, Board, Piece, Lock); {get_lock, Node} -> Node ! {give_lock, Lock}, data(OtherPlayer, Board, Piece, Lock); {set_board, NewBoard} -> data(OtherPlayer, NewBoard, Piece, Lock); {set_lock, NewLock} -> data(OtherPlayer, Board, Piece, NewLock) end. % Initial state with only other player's node data(OtherPlayer) -> receive {get_player, Node} -> Node ! {give_player, OtherPlayer}, data(OtherPlayer); {set_up_board, NewBoard, NewPiece, Lock} -> data(OtherPlayer, NewBoard, NewPiece, Lock) end. %%% Two clients generated that talk to each other, one on both systems client() -> receive % Add Other_Player to our data process, send back our node+client {connect, Other_Player} -> io:fwrite("Connected to Other Player, ~p~n", [Other_Player]), % Spawn new data process DataPID = spawn(t3_v2, data, [Other_Player]), % Set up global name for data process register(data, DataPID), Other_Player ! {connected, self()}; % Connected to Other_Player, add full node+client process to our % data process {connected, Other_Player} -> io:fwrite("Connected to Other Player, ~p~n", [Other_Player]), % Spawn new data process DataPID = spawn(t3_v2, data, [Other_Player]), % Set up global name for data process register(data, DataPID), % Decide who gets to go first % Change seed to something random enough random:seed(now()), My_Num = random:uniform(), Other_Num = random:uniform(), if My_Num >= Other_Num -> io:fwrite("'~p' >= '~p'; You begin game...~n", [My_Num, Other_Num]), Other_Player ! {print_whobegins, My_Num, Other_Num}, generate_game(); My_Num < Other_Num -> io:fwrite("'~p' < '~p'; Other Player begins game...~n", [My_Num, Other_Num]), Other_Player ! {print_whobegins, My_Num, Other_Num}, Other_Player ! {gen_game}; true -> ok end; % Prints who begins the game for the node that created the new game first % Player 2 being the one that connected to Player 1 {print_whobegins, Other_Num, My_Num} -> if Other_Num >= My_Num -> io:fwrite("'~p' >= '~p'; Other Player begins game...~n", [Other_Num, My_Num]); true -> io:fwrite("'~p' < '~p'; You begin game...~n", [Other_Num, My_Num]) end; {set_up_board} -> % Creates board Board = new_board(), % Sets piece Piece = o, % Sets LOCK Lock = closed, % Grabs data process Data = whereis(data), % Set our board piece to 'o' and lock set to closed Data ! {set_up_board, Board, Piece, Lock}, io:fwrite("You are 'o'.~n"), io:fwrite("Other player is making move..Please wait~n"); {message, Message} -> io:fwrite("Other Player ~p says: ~p~n", [get_player(), Message]); {gen_game} -> io:fwrite("Generating game...~n~n"), generate_game(); {results, Board, Result} -> % 0 for tie, 1 for other wins io:fwrite("~n~n$$$$$$$$$$$$FINAL BOARD$$$$$$$$$$$~n~n"), print_board(Board), if Result == 0 -> io:fwrite("~nIt's a tie! Thanks for playing!~n"); true -> io:fwrite("~nSorry! You lose, Player '~p'!~n", [get_piece()]) end; {your_turn, Coordinate, NewBoard} -> set_board(NewBoard), io:fwrite("~n~nOther Player set coordinate '~p'~n~n", [Coordinate]), print_board(NewBoard), %%% unset lock set_lock(open), Piece = get_piece(), io:fwrite("~nYou are '~p'. Use 'placetoken(a1...c3)' to place token on board.~n", [Piece]) end, client(). %%% Generates the board, piece, and lock and sends to data process generate_game() -> % Creates board Board = new_board(), % Sets piece Piece = x, % Sets LOCK Lock = open, % Grabs data process Data = whereis(data), % Set our board piece to 'x' and lock set to open Data ! {set_up_board, Board, Piece, Lock}, % Send message to other node that they must wait, and that they are 'o' Other = get_player(), Other ! {set_up_board}, % Print message with board print_board(Board), io:fwrite("~nYou are 'x'. Use 'placetoken(a1...c3)' to place token on board.~n"). %%% Below are numerous getters and setters %%% Self explanatory get_player() -> Data = whereis(data), Data ! {get_player, self()}, receive {give_player, OtherPlayer} -> OtherPlayer end. get_board() -> Data = whereis(data), Data ! {get_board, self()}, receive {give_board, Board} -> Board end. get_piece() -> Data = whereis(data), Data ! {get_piece, self()}, receive {give_piece, Piece} -> Piece end. get_lock() -> Data = whereis(data), Data ! {get_lock, self()}, receive {give_lock, Lock} -> Lock end. set_board(Board) -> Data = whereis(data), Data ! {set_board, Board}. set_lock(Lock) -> Data = whereis(data), Data ! {set_lock, Lock}. %%% Possibly the most complicated function, placetoken places a token (a1... c3) %%% Onto the board. Locks, already placed pieces, and winning states are assessed here placetoken(Coordinate) -> Board = get_board(), Piece = get_piece(), Lock = get_lock(), Possible = {a1, a2, a3, b1, b2, b3, c1, c2, c3}, Is_Poss = lists:member(Coordinate, tuple_to_list(Possible)), if Lock == closed -> io:fwrite("It is not your turn yet. Please wait.~n"); Is_Poss -> io:fwrite("~p chosen.~n", [Coordinate]), Free = check_element(Coordinate, Board), if Free == false -> io:fwrite("Please choose a free space~n"); true -> % Switched Y and X for compatability {Y, X} = convert_coordinate(Coordinate), New_Board = place_element(Piece, X, Y, Board), set_board(New_Board), % Check board to see if you won Won = check(get_board()), Other = get_player(), if {victory, x} == Won -> io:fwrite("~n~n$$$$$$$$$$$$FINAL BOARD$$$$$$$$$$$~n~n"), print_board(get_board()), io:fwrite("~nCongratulations! You won, Player '~p'~n", [get_piece()]), Other ! {results, get_board(), 1}; {victory, o} == Won -> io:fwrite("~n~n$$$$$$$$$$$$FINAL BOARD$$$$$$$$$$$~n~n"), print_board(get_board()), io:fwrite("~nCongratulations! You won, Player '~p'~n", [get_piece()]), Other ! {results, get_board(), 1}; draw == Won -> io:fwrite("~n~n$$$$$$$$$$$$FINAL BOARD$$$$$$$$$$$~n~n"), print_board(get_board()), io:fwrite("~nIt's a tie! Thanks for playing!~n"), Other ! {results, get_board(), 1}; true -> print_board(get_board()), Other ! {your_turn, Coordinate, get_board()}, % SET LOCK set_lock(closed) end, ok end; true -> io:fwrite("No correct unit chosen. Correct units include: ~p.~n", [Possible]) end. %%% Converts coordinate atom into {X, Y} numerical tuple convert_coordinate(Co) -> Co_str = atom_to_list(Co), Co_str_0 = string:substr(Co_str, 1, 1), Co_str_1 = string:substr(Co_str, 2, 1), if Co_str_0 == "a" -> X = 1; Co_str_0 == "b" -> X = 2; Co_str_0 == "c" -> X = 3; true -> X = -1 end, if Co_str_1 == "1" -> Y = 1; Co_str_1 == "2" -> Y = 2; Co_str_1 == "3" -> Y = 3; true -> Y = -1 end, {X, Y}. %%% prints the board by calling greater function below print_board(Board) -> print_board(Board, 1). %%% Prints the board, recursively going line by line, piece by piece print_board(Board, Index) -> if Index < 10 -> L = element(Index, Board); true -> L = undefined end, New_Index = Index + 1, case Index of 1 -> io:fwrite("#############~n"), io:fwrite("# ~p ", [L]), print_board(Board, New_Index); 2 -> io:fwrite(" ~p ", [L]), print_board(Board, New_Index); 3 -> io:fwrite(" ~p #~n", [L]), print_board(Board, New_Index); 4 -> io:fwrite("# ~p ", [L]), print_board(Board, New_Index); 5 -> io:fwrite(" ~p ", [L]), print_board(Board, New_Index); 6 -> io:fwrite(" ~p #~n", [L]), print_board(Board, New_Index); 7 -> io:fwrite("# ~p ", [L]), print_board(Board, New_Index); 8 -> io:fwrite(" ~p ", [L]), print_board(Board, New_Index); 9 -> io:fwrite(" ~p #~n", [L]), io:fwrite("#############~n"), print_board(Board, New_Index); _ -> ok end. %%% Generates a new board and returns it new_board() -> {l, l, l, l, l, l, l, l, l}. %%% Places an element on the board based on the X, Y coordinate place_element(Who, X, Y, Board) -> setelement((Y - 1) * 3 + X, Board, Who). %%% Checks the element in question to make sure it is not overwritten %%% This is also done with pattern matching check_element(Element, Board) -> if Element == a1 -> {Key, _, _, _, _, _, _, _, _} = Board; Element == a2 -> {_, Key, _, _, _, _, _, _, _} = Board; Element == a3 -> {_, _, Key, _, _, _, _, _, _} = Board; Element == b1 -> {_, _, _, Key, _, _, _, _, _} = Board; Element == b2 -> {_, _, _, _, Key, _, _, _, _} = Board; Element == b3 -> {_, _, _, _, _, Key, _, _, _} = Board; Element == c1 -> {_, _, _, _, _, _, Key, _, _} = Board; Element == c2 -> {_, _, _, _, _, _, _, Key, _} = Board; Element == c3 -> {_, _, _, _, _, _, _, _, Key} = Board; true -> io:fwrite("Should never get here~n"), Key = undefined end, if Key == x -> io:fwrite("Place ~p taken by 'x'~n", [Element]), false; Key == o -> io:fwrite("Place ~p taken by 'o'~n", [Element]), false; true -> io:fwrite("Place ~p is free.~n", [Element]), true end. %%% Checks to see if payer has won the game or a tie %%% Basically just matches the board with a prewritten board check(Board) -> case Board of {x, x, x, _, _, _, _, _, _} -> {victory, x}; {_, _, _, x, x, x, _, _, _} -> {victory, x}; {_, _, _, _, _, _, x, x, x} -> {victory, x}; {x, _, _, x, _, _, x, _, _} -> {victory, x}; {_, x, _, _, x, _, _, x, _} -> {victory, x}; {_, _, x, _, _, x, _, _, x} -> {victory, x}; {x, _, _, _, x, _, _, _, x} -> {victory, x}; {_, _, x, _, x, _, x, _, _} -> {victory, x}; {o, o, o, _, _, _, _, _, _} -> {victory, o}; {_, _, _, o, o, o, _, _, _} -> {victory, o}; {_, _, _, _, _, _, o, o, o} -> {victory, o}; {o, _, _, o, _, _, o, _, _} -> {victory, o}; {_, o, _, _, o, _, _, o, _} -> {victory, o}; {_, _, o, _, _, o, _, _, o} -> {victory, o}; {o, _, _, _, o, _, _, _, o} -> {victory, o}; {_, _, o, _, o, _, o, _, _} -> {victory, o}; {A, B, C, D, E, F, G, H, I} when A =/= l, B =/= l, C =/= l, D =/= l, E =/= l, F =/= l, G =/= l, H =/= l, I =/= l -> draw; _ -> ok end.