% Author: HRC % Date: 8/30/2002 % Strategy: % % Pursue the following goals, in this order of priority: % % 1. If you have a package and are at its destination, drop it % 2. If you are over a package and have capacity, pick it up % 3. If you have a package, go to it's destination % 4. If you know where a package or base is, go there % 5. Go to every square on the board (hey, we might have missed a package) % 6. Stand still (just in case 1-5 didn't work for some reason % % The obvious next level of development would be to add more sophisticated % strategies. The program is set up to easily allow what-if investigation, % although this implementation doesn't use that. This is why all the transient % state is kept in a list, but the more permanent state is kept as fact. % There is currently no strategy whatsoever for dealing with other bots. % Tactics: % % There are a number of approaches taken in local contexts to make things % work more efficiently, or to avoid infinite interactions with other bots. % These are not prioritized; they just happen when the circumstances arise. % % * Whenever choosing from a list of destinations, pick the closest one % * When going to a destination, first figure out an optimal route (which % sometimes takes a lot of CPU time), then quickly follow it % * If you get pushed along the way, restart the strategy process (see % above); things may have changed % * If you get pushed while trying to pick up a package (unless you did % manage to get at least one), idle for a couple turns; this avoids the % case of two bots both trying to pick up the same package and % constantly pushing each other, even if unintentionally, for the rest % of the game; as with any good-neighbor policy, it can be taken % advantage of, but the downside of not having this is a game-long % deadlock (note: in watching the test sessions, I noticed some evil % bots seemed to wait by a package and then push everyone who tried % to pick it up (but never pick it up themselves); given this, it % would be nice to give up after a while and go find another package, % but I didn't have time to implement that) % * If you get pushed, do a couple random moves; this can waste a lot of % turns in crowded situations, but it randomizes a number of potential % deadlock situations (including the simple butt heads one), and may % actually be effective in shaking a hostile stalker % * Bid low (we never bid more than 3); I don't know of any good % algorithms for larger bidding, so go for maximum life % * On the other hand, try to avoid being pushed keeping you from doing % the action you wanted: % - if there's a bot next to you (imminent push potential), bid 3 % - if there's a bot two away (push after move potential), bid -2 % % Many more tactical pieces could be added (and these refined) given time % to watch the bot interact with other bots. Were this a longer-time-frame % activity, I would expect an evolution of tactics over time, but for the % available time, these may do. % Persistent facts: % bd_passable(X,Y) % bd_impassable(X,Y) % bd_death(X,Y) % bd_base(X, Y) (but it does get retracted when we've been there) % robot_id(RID) % robot_cap(C) % robot_money(M) % pkg_weight(PID, W) % pkg_dest(PID, X, Y) (in continuous server mode, this gets updated) :- dynamic bd_passable/2, bd_impassable/2, bd_death/2, bd_base/2. :- dynamic robot_id/1, robot_cap/1, robot_money/1. :- dynamic pkg_weight/2, pkg_dest/3. % State list objects: % bot(RID, X, Y) % pkg(PID, X, Y) (if it's on the ground) % pkg(PID, RID) (if it's on a bot) %%%%%%%%% Console stuff: % Main goal for shell invocation runme :- current_prolog_flag(argv, Flags), nth1(4, Flags, User), User = '-', nth1(2, Flags, Host), nth1(3, Flags, PortAtom), atom_to_int(PortAtom, Port), create_client(Host, Port, user), halt. runme :- current_prolog_flag(argv, Flags), nth1(2, Flags, Host), nth1(3, Flags, PortAtom), atom_to_int(PortAtom, Port), open_null_stream(Stream), create_client(Host, Port, Stream), close(Stream), halt. % Run and write to console run :- create_client('icfp1.cse.ogi.edu', 22010, user). % Run and write to log file frun :- open('logfile.txt', write, Stream), create_client('icfp1.cse.ogi.edu', 22010, Stream), close(Stream). % Run silently srun :- open_null_stream(Stream), create_client('icfp1.cse.ogi.edu', 22001, Stream), close(Stream). %%%%%%%%% Client top-level: % Open socket/run/close socket create_client(Host, Port, LogFd) :- tcp_socket(Socket), tcp_connect(Socket, Host:Port), tcp_open_socket(Socket, ReadFd, WriteFd), assert(svr_rd(ReadFd)), assert(svr_wr(WriteFd)), assert(log_wr(LogFd)), handle_client, retract(svr_rd(ReadFd)), retract(svr_wr(WriteFd)), retract(log_wr(LogFd)), close(ReadFd), close(WriteFd). % Initialization, plus exit point for indirect exits handle_client :- initialize_board, read_config, read_response([], S), read_packages(S, NewStates), block(client_top_block, make_moves(NewStates), _). %%%%%%%%% Move making: % Start making moves; plus retry point for execpected sequence aborts make_moves(OldStates) :- block(try_another_one, figure_out_move(OldStates), NewStates), !, make_moves(NewStates). % Get our ID and location and try find_move figure_out_move(OldStates) :- %line_log('Moving based on state '), line_log(OldStates), robot_id(RID), get_robot_state(RID, OldStates, bot(_, X, Y)), find_move(OldStates, RID, X, Y, NewStates), exit(try_another_one, NewStates). % In place to drop, so do it find_move(OldStates, RID, X, Y, NewStates) :- findall(PID, (member(pkg(PID, RID), OldStates), pkg_dest(PID, X, Y)), PIDs), length(PIDs, PIDsLen), PIDsLen \= 0, robot_drop(OldStates, PIDs, NewStates). % Over a package, so pick it up find_move(OldStates, _, X, Y, NewStates) :- findall(D, (member(pkg(PID, X, Y), OldStates), pkg_dest(PID, XDest, YDest), taxi_dist(X, Y, XDest, YDest, N), D = N - PID), Ds), keysort(Ds, DsSorted), findall(P, member(_ - P, DsSorted), PIDs), cap_available(OldStates, C), select_for_weight(PIDs, C, SelectedPIDs), length(SelectedPIDs, PIDsLen), PIDsLen \= 0, robot_pickup(OldStates, SelectedPIDs, S), check_pickup_ToW(S, SelectedPIDs, NewStates). % Got a package, so go deliver it find_move(OldStates, RID, _, _, NewStates) :- robot_id(RID), get_robot_state(RID, OldStates, bot(_, X, Y)), findall(D, (member(pkg(PID, RID), OldStates), pkg_dest(PID, XDest, YDest), taxi_dist(X, Y, XDest, YDest, N), D = N - xy(XDest, YDest)), Ds), length(Ds, DsLen), DsLen \= 0, keysort(Ds, DsSorted), member(_ - xy(XTo, YTo), DsSorted), robot_goto(OldStates, XTo, YTo, NewStates). % Know of a package or base location, so go there find_move(OldStates, _, _, _, NewStates) :- robot_id(RID), get_robot_state(RID, OldStates, bot(_, X, Y)), findall(D, ((member(pkg(_, XDest, YDest), OldStates); bd_base(XDest, YDest)), taxi_dist(X, Y, XDest, YDest, N), D = N - xy(XDest, YDest)), Ds), length(Ds, DsLen), DsLen \= 0, keysort(Ds, DsSorted), member(_ - xy(XTo, YTo), DsSorted), robot_goto(OldStates, XTo, YTo, NewStates). % Nothing to do, so go visit every space in case there's a package we missed find_move(OldStates, _, _, _, NewStates) :- findall(Loc, (bd_passable(X, Y), Loc = xy(X,Y)), Locs), goto_all(OldStates, Locs, NewStates). % In the unlikely event all of the above doesn't work, just stand still find_move(OldStates, _, _, _, NewStates) :- robot_idle_one(OldStates, NewStates). % Given a package ID list and a capacity, return a list of packages that % can be picked up select_for_weight([], _, []). select_for_weight([PID | PIDRest], C, [PID | SelectedPIDRest]) :- pkg_weight(PID, W), W =< C, C2 is C-W, select_for_weight(PIDRest, C2, SelectedPIDRest). select_for_weight([_ | PIDRest], C, SelectedPIDs) :- select_for_weight(PIDRest, C, SelectedPIDs). % Tactics: avoid getting into an infinite back-and-forth check_pickup_ToW(S, PIDs, S) :- robot_id(RID), member(PID, PIDs), member(pkg(PID, RID), S). check_pickup_ToW(OldStates, _, NewStates) :- % The pick-up didn't work; someone must have pushed us % It could be we're both trying to pick up and keep pushing each other % Idle a moment and let him get out of our way line_log('Tug-of-war mayhaps; idling'), robot_idle_move(OldStates, S1), robot_idle_move(S1, S2), robot_idle_move(S2, S3), robot_idle_move(S3, NewStates). % Go to every xy(X, Y) in the list goto_all(S, [], S). goto_all(S, _, S) :- member(pkg(_, _, _), S), exit(try_another_one, S). goto_all(OldStates, [Loc | LocsRest], NewStates) :- Loc = xy(X,Y), robot_goto(OldStates, X, Y, S), goto_all(S, LocsRest, NewStates). %%%%%%%%% Robot control: % Go to (X, Y), figuring out the best route to get there robot_goto(OldStates, X, Y, NewStates) :- bd_passable(X, Y), /*not(member(bot(_, X, Y), OldStates)),*/ robot_id(ID), get_robot_state(ID, OldStates, bot(_, XR, YR)), route_to(OldStates, XR, YR, X, Y, Route), length(Route, RouteLen), RouteLen \= 0, line_log('Robot to follow route:'), line_log(Route), route_move(OldStates, Route, NewStates). % Pick up a list of packages robot_pickup(OldStates, PIDs, NewStates) :- defensive_bid(OldStates, Bid), concat_atom(PIDs, ' ', PIDList), concat_atom([Bid, ' Pick ', PIDList], Move), take_turn(OldStates, Move, NewStates). % Drop a list of packages robot_drop(OldStates, PIDs, NewStates) :- defensive_bid(OldStates, Bid), concat_atom(PIDs, ' ', PIDList), concat_atom([Bid, ' Drop ', PIDList], Move), take_turn(OldStates, Move, NewStates). % Do the turn-common stuff take_turn(OldStates, Move, NewStates) :- line_out(Move), read_response(OldStates, S), read_packages(S, NewStates). % Move through a list of vectors route_move(S, [], S). route_move(OldStates, [v(XFrom, YFrom, XTo, YTo) | Rest], NewStates) :- move_direction(XFrom, YFrom, XTo, YTo, Dir), defensive_bid(OldStates, Bid), concat_atom([Bid, ' Move ', Dir], Move), take_turn(OldStates, Move, S), % Did we get there? If not, we don't know what's where; start over abort_if_not_at(S, XTo, YTo), route_move(S, Rest, NewStates). % Tactics: check that we're where we want to be; if not, we got pushed, % in which case it's best to start the whole what-to-do process over; but % first do a couple random moves to avoid deadlocks with other bots (this % could waste time, but it could also avoid deadlock situations that would % waste the rest of the game) abort_if_not_at(States, XTo, YTo) :- robot_id(ID), get_robot_state(ID, States, bot(_, X, Y)), X = XTo, Y = YTo. abort_if_not_at(States, _, _) :- line_log('Pushed and unhappy about it!'), % Random move a couple times in case we're in a deadlock robot_idle_move(States, S1), robot_idle_move(S1, S2), exit(try_another_one, S2). % Do a random move to an open adjacent space robot_idle_move(OldStates, NewStates) :- robot_id(ID), get_robot_state(ID, OldStates, bot(_, X, Y)), adjacent_vectors(OldStates, X, Y, Vs), length(Vs, VsLen), VsLen \= 0, N is random(VsLen), nth0(N, Vs, V), V = v(_, _, XTo, YTo), robot_goto(OldStates, XTo, YTo, NewStates). robot_idle_move(OldStates, NewStates) :- robot_idle_one(OldStates, NewStates). % Idle forever (mainly used for debugging) robot_idle(OldStates, NewStates) :- robot_idle_one(OldStates, S), robot_idle(S, NewStates). % Idle one move robot_idle_one(OldStates, NewStates) :- take_turn(OldStates, '1 Drop 0', NewStates). % Figure out what to bid to try to avoid getting pushed inopportunely defensive_bid(States, Bid) :- findall(D, (member(bot(_, X, Y), States), taxi_dist(States, X, Y, D)), Ds), defensive_bid_at_Ds(Ds, Bid). defensive_bid_at_Ds(Ds, Bid) :- member(1, Ds), Bid is 3. defensive_bid_at_Ds(Ds, Bid) :- member(2, Ds), Bid is -2. defensive_bid_at_Ds(_, Bid) :- Bid is 1. %%%%%%%%% Finding routes: % Find a list of vectors leading from (XFrom, YFrom) to (XTo, YTo) route_to(_, X, Y, X, Y, []). route_to(States, XFrom, YFrom, XTo, YTo, Route) :- adjacent_vectors(States, XFrom, YFrom, AV), vectors_to(States, XTo, YTo, AV, AV, Vectors), vectors_to_route(XFrom, YFrom, XTo, YTo, Vectors, RouteRev), reverse(RouteRev, Route). % Given a list of existing vectors, find vectors branching from there, % stopping when you reach (X, Y); EdgeVectors are the vectors on the % outer edge of the vector list (and hence, the place to add new vectors % from) vectors_to(_, X, Y, OldVectors, _, OldVectors) :- member(v(_, _, X, Y), OldVectors). vectors_to(States, X, Y, OldVectors, EdgeVectors, NewVectors) :- % Find all the adjacency sets that work from those vectors findall(AVs, (member(EV, EdgeVectors), EV = v(_, _, XV, YV), adjacent_vectors(States, XV, YV, AVs), length(AVs, AVsLen), AVsLen \= 0), VV), % Flatten it to a simple list flatten(VV, NewVs), % If there's nothing left, we've failed length(VV, VVLen), VVLen \= 0, % Add them (removing links to existing nodes), and get the list of new vectors add_no_dup_dest(NewVs, OldVectors, NewVsUsed, NV), !, vectors_to(States, X, Y, NV, NewVsUsed, NewVectors). % Add in new vectors, avoiding ones that lead back to where we've been add_no_dup_dest([], OldVectors, [], OldVectors). add_no_dup_dest([V | R], OldVectors, [V | NewVsUsed], NewVectors) :- V = v(_, _, XTo, YTo), not(member(v(XTo, YTo, _, _), OldVectors)), not(member(v(_, _, XTo, YTo), OldVectors)), append(OldVectors, [V], NV), !, add_no_dup_dest(R, NV, NewVsUsed, NewVectors). add_no_dup_dest([_ | R], OldVectors, NewVsUsed, NewVectors) :- add_no_dup_dest(R, OldVectors, NewVsUsed, NewVectors). % Find the vectors to adjacent spaces, but only to passable spaces adjacent_vectors(_, X, Y, Vectors) :- XUp is X+1, XDown is X-1, YLeft is Y-1, YRight is Y+1, Candidates = [v(X, Y, XUp, Y), v(X, Y, XDown, Y), v(X, Y, X, YLeft), v(X, Y, X, YRight)], findall(V, (member(V, Candidates), V = v(_, _, XV, YV), bd_passable(XV, YV)), Vectors). % Prune the vector list down to a single route; note: this returns the % reverse route, which is inverted by the caller vectors_to_route(X, Y, X, Y, _, []). vectors_to_route(XFrom, YFrom, XTo, YTo, Vectors, [Step | RouteRest]) :- member(Step, Vectors), Step = v(X, Y, XTo, YTo), vectors_to_route(XFrom, YFrom, X, Y, Vectors, RouteRest). % Figure the move letter for a move in a particular direction move_direction(XFrom, YFrom, XTo, YTo, Dir) :- XDir is XTo-XFrom, YDir is YTo-YFrom, xy_move_dir(XDir, YDir, Dir). xy_move_dir(XDir, _, Dir) :- XDir > 0, Dir = 'E'. xy_move_dir(XDir, _, Dir) :- XDir < 0, Dir = 'W'. xy_move_dir(_, YDir, Dir) :- YDir > 0, Dir = 'N'. xy_move_dir(_, YDir, Dir) :- YDir =< 0, Dir = 'S'. % Taxi-cab geometry distance function taxi_dist(XFrom, YFrom, XTo, YTo, D) :- D is abs(XTo-XFrom) + abs(YTo-YFrom). taxi_dist(States, XTo, YTo, D) :- robot_id(ID), get_robot_state(ID, States, bot(_, XFrom, YFrom)), taxi_dist(XFrom, YFrom, XTo, YTo, D). %%%%%%%%% Initial input/config: % Get and record the board initialize_board :- line_out('Player'), line_in(BoardDimIn), line_to_list(BoardDimIn, BoardDim), nth1_number(1, BoardDim, XSize), nth1_number(2, BoardDim, YSize), assert(board_size([XSize, YSize])), assert(bd_impassable(0,_)), assert(bd_impassable(_,0)), assert(bd_impassable(X,_) :- X > XSize), assert(bd_impassable(_,Y) :- Y > YSize), read_board_lines(1, YSize). % Read one board line read_board_lines(_, 0). read_board_lines(LineNum, NumLeft) :- line_in(Line), process_board_line(Line, LineNum, 1), LineNum2 is LineNum+1, NumLeft2 is NumLeft-1, read_board_lines(LineNum2, NumLeft2). % Process the line character by character process_board_line([], _, _). process_board_line([X | R], LineNum, ColNum) :- process_board_char(X, LineNum, ColNum), ColNum2 is ColNum+1, process_board_line(R, LineNum, ColNum2). % Empty space process_board_char('.', LineNum, ColNum) :- assert(bd_passable(ColNum, LineNum)). % Water process_board_char('~', LineNum, ColNum) :- assert(bd_impassable(ColNum, LineNum)), assert(bd_death(ColNum, LineNum)). % Wall process_board_char('#', LineNum, ColNum) :- assert(bd_impassable(ColNum, LineNum)). % Base process_board_char('@', LineNum, ColNum) :- assert(bd_passable(ColNum, LineNum)), assert(bd_base(ColNum, LineNum)). % Read the configuration info read_config :- line_in(ConfigLine), line_to_list(ConfigLine, Config), nth1_atom(1, Config, IDNum), atom_concat('#', IDNum, ID), assert(robot_id(ID)), nth1_number(2, Config, Capacity), assert(robot_cap(Capacity)), nth1_number(3, Config, Money), assert(robot_money(Money)). %%%%%%%%% Turn server responses: % Read a server response read_response(OldStates, NewStates) :- line_in(RespLine), line_to_list(RespLine, Resp), response(OldStates, S, RIDs, Resp, []), findall(X, (member(X, S), not(functor(X, bot, _))), SMinusR), findall(Y, (member(Y, S), Y = bot(RID, _, _), member(RID, RIDs)), SOKBots), append(SMinusR, SOKBots, NewStates). % Response DCG response(OldStates, NewStates, RIDs) --> response_update(OldStates, S, RIDs1), response(S, NewStates, RIDs2), {append(RIDs1, RIDs2, RIDs)}. response(S, S, []) --> []. % One robot's entry in the response response_update(S, S, []) --> [robot, _, 'died.'], {!, line_in(_), line_in(_), line_in(_), exit(client_top_block, true)}. response_update(OldStates, NewStates, [RobotID]) --> [RobotID], {get_robot_state(RobotID, OldStates, OldBotState)}, response_items(OldStates, OldBotState, S1, NewBotState), {delete(S1, OldBotState, S2), append(S2, [NewBotState], NewStates)}. % The items of the robot's entry response_items(OldStates, OldBotState, NewStates, NewBotState) --> bot_response_item(OldBotState, S), response_items(OldStates, S, NewStates, NewBotState). response_items(OldStates, OldBotState, NewStates, NewBotState) --> pkg_response_item(OldStates, OldBotState, S), response_items(S, OldBotState, NewStates, NewBotState). response_items(S, BS, S, BS) --> []. % Bot-related items (N, S, E, W, X Y) bot_response_item(OldBotState, NewBotState) --> [n], {OldBotState = bot(ID, X, Y), Y2 is Y+1, NewBotState = bot(ID, X, Y2)}. bot_response_item(OldBotState, NewBotState) --> [s], {OldBotState = bot(ID, X, Y), Y2 is Y-1, NewBotState = bot(ID, X, Y2)}. bot_response_item(OldBotState, NewBotState) --> [e], {OldBotState = bot(ID, X, Y), X2 is X+1, NewBotState = bot(ID, X2, Y)}. bot_response_item(OldBotState, NewBotState) --> [w], {OldBotState = bot(ID, X, Y), X2 is X-1, NewBotState = bot(ID, X2, Y)}. bot_response_item(OldBotState, NewBotState) --> [x, XNew, y, YNew], {OldBotState = bot(ID, _, _), atom_to_int(XNew, XNum), atom_to_int(YNew, YNum), NewBotState = bot(ID, XNum, YNum)}. % Package-related items (P, D) pkg_response_item(OldStates, OldBotState, NewStates) --> [p, PackageID], {remove_package(OldStates, PackageID, S), OldBotState = bot(ID, _, _), append(S, [pkg(PackageID, ID)], NewStates)}. pkg_response_item(OldStates, OldBotState, NewStates) --> [d, PackageID], {remove_package(OldStates, PackageID, S), OldBotState = bot(_, X, Y), append_package(S, PackageID, X, Y, NewStates)}. % Get the state of our robot (clean-up note: this should really just % return the bot's (X, Y), since that's what we're interested in get_robot_state(ID, OldStates, State) :- member(State, OldStates), State = bot(ID, _, _). get_robot_state(ID, _, bot(ID, -1000, -1000)). %%%%%%%%% Packages: % Reac a package list read_packages(OldStates, NewStates) :- line_in(PackLine), line_to_list(PackLine, LinearList), check_end_of_game(LinearList), robot_id(RID), get_robot_state(RID, OldStates, bot(_, XR, YR)), findall(St, (member(St, OldStates), not(St = pkg(_, XR, YR))), S), parse_packages(S, LinearList, NewStates), update_bases(NewStates, LinearList). % Check for the end-of-game info message; abort if found check_end_of_game([robot, _, 'died.']) :- !, line_in(_), line_in(_), line_in(_), exit(client_top_block, true). check_end_of_game(_). % Parse the package list parse_packages(S, [], S). parse_packages(OldStates, [ID, XAtom, YAtom, WAtom | LinearRest], NewStates) :- atom_to_int(XAtom, XNum), atom_to_int(YAtom, YNum), atom_to_int(WAtom, WNum), robot_id(RID), get_robot_state(RID, OldStates, bot(_, XR, YR)), update_package_state(OldStates, ID, XR, YR, S), update_package_weight_dest(ID, WNum, XNum, YNum), parse_packages(S, LinearRest, NewStates). % Remember where we the package is update_package_state(S, ID, _, _, S) :- member(P, S), P = pkg(ID, _, _). update_package_state(OldStates, ID, X, Y, NewStates) :- append(OldStates, [pkg(ID, X, Y)], NewStates). % Remember the package's weight and destination update_package_weight_dest(ID, W, X, Y) :- pkg_weight(ID, W), pkg_dest(ID, X, Y). update_package_weight_dest(ID, W, X, Y) :- pkg_weight(ID, _), retract(pkg_weight(ID, _)), retract(pkg_dest(ID, _, _)), update_package_weight_dest(ID, W, X, Y). update_package_weight_dest(ID, W, X, Y) :- assert(pkg_weight(ID, W)), assert(pkg_dest(ID, X, Y)). % Remove the package from the states remove_package(OldStates, PackageID, NewStates) :- member(P, OldStates), (P = pkg(PackageID, _); P = pkg(PackageID, _, _)), delete(OldStates, P, NewStates). remove_package(S, _, S). % Append the package to the states (unless it's at it's destination) append_package(OldStates, PackageID, X, Y, NewStates) :- not(pkg_dest(PackageID, X, Y)), append(OldStates, [pkg(PackageID, X, Y)], NewStates). append_package(S, _, _, _, S). % If we're at the base, retract it; they're only needed to get us to the % packages, and now we know about all the packages here update_bases(States, []) :- robot_id(RID), get_robot_state(RID, States, bot(_, X, Y)), bd_base(X, Y), retract(bd_base(X, Y)). update_bases(_, _). % Return the remaining available carrying capacity cap_available(States, N) :- robot_cap(C), cap_used(States, N2), N is C-N2. % Add up our used carrying capacity cap_used(States, N) :- robot_id(RID), findall(W, (member(pkg(PID, RID), States), pkg_weight(PID, W)), Ws), sum(Ws, N). % Sum a list of numbers sum([], 0). sum([H | R], N) :- sum(R, N2), N is H+N2. %%%%%%%%% Lower level I/O: % Read a line from the server line_in(Line) :- svr_rd(RdFd), log_wr(LogFd), line_in(RdFd, LogFd, Line), !. line_in(ReadFd, LogFd, [Clc | Rest]) :- get_char(ReadFd, C), C \= '\012', C \= end_of_file, downcase_atom(C, Clc), put_char(LogFd, C), line_in(ReadFd, LogFd, Rest). line_in(_, LogFd, []) :- nl(LogFd). clear_line_in :- sleep(1), svr_rd(RdFd), clear_line_in(RdFd). clear_line_in(RdFd) :- at_end_of_stream(RdFd). clear_line_in(RdFd) :- line_out(''), line_in(_), clear_line_in(RdFd). % Convert a line to a list of atoms line_to_list([], []). line_to_list(Line, List) :- atom_chars(A, Line), concat_atom(List, ' ', A). % Access the list and convert the item to a number nth1_number(N, List, Number) :- nth1(N, List, NAtom), atom_to_int(NAtom, Number). nth1_atom(N, List, NAtom) :- nth1(N, List, NAtom). % Convert an atom to an integer atom_to_int(Atom, Int) :- atom_chars(Atom, Chars), number_chars(Int, Chars). % Write a line to the server line_out(Line) :- svr_wr(WrFd), log_wr(LogFd), write(WrFd, Line), put_char(WrFd, 10), flush_output(WrFd), write(LogFd, '>'), write(LogFd, Line), nl(LogFd). % Write a line to the log line_log([]) :- log_wr(LogFd), write(LogFd, '!'), nl(LogFd). line_log([A | R]) :- log_wr(LogFd), write(LogFd, '!'), write(LogFd, A), line_log_rest(R). line_log(Line) :- log_wr(LogFd), write(LogFd, '!'), write(LogFd, Line), nl(LogFd). line_log_rest([]) :- log_wr(LogFd), nl(LogFd). line_log_rest([A | R]) :- log_wr(LogFd), write(LogFd, A), line_log_rest(R).