Skip to content

Instantly share code, notes, and snippets.

@gorkinovich
Last active May 29, 2022 14:53
Show Gist options
  • Select an option

  • Save gorkinovich/b11a174f2fa3005d1d4a51d7217a1348 to your computer and use it in GitHub Desktop.

Select an option

Save gorkinovich/b11a174f2fa3005d1d4a51d7217a1348 to your computer and use it in GitHub Desktop.
This program solves the Myst puzzle inside the clock tower.
%%%================================================================================
%%% Copyright (c) 2022 Gorka Suárez García
%%%
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
%%% of this software and associated documentation files (the "Software"), to deal
%%% in the Software without restriction, including without limitation the rights
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
%%% copies of the Software, and to permit persons to whom the Software is
%%% furnished to do so, subject to the following conditions:
%%%
%%% The above copyright notice and this permission notice shall be included in
%%% all copies or substantial portions of the Software.
%%%
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE.
%%%================================================================================
%%%================================================================================
%%% This program solves the Myst puzzle inside the clock tower.
%%% Open the Erlang console and complile the module with:
%%% c(myst).
%%% Then you can execute the program with:
%%% myst:run().
%%% This will give you one of the best solutions to the puzzle.
%%% You also can get the first solution with:
%%% myst:run(first).
%%% Or get all the solutions with:
%%% myst:run(all).
%%%================================================================================
-module(myst).
-author("Gorka Suárez García").
-behaviour(gen_server).
-export([run/0, run/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(LIMIT, 9).
-define(GOAL, {2, 2, 1}).
-define(DEBUG, false).
-define(SERVER, ?MODULE).
-record(data_state, {results = []}).
%%%================================================================================
%%% Main
%%%================================================================================
run() -> run(best).
run(first) -> io:format("Result: ~w~n", [find_first(1, {3, 3, 3}, [])]);
run(all) -> run_all(fun get_all_results/0);
run(_) -> run_all(fun get_best_result/0).
run_all(GetResult) ->
start_link(),
find_all(1, {3, 3, 3}, []),
case GetResult() of
{solution, Victim} ->
io:format("Solution:~p~n", [Victim]);
Victims when is_list(Victims) ->
io:format("Solutions:~n~p~n", [Victims]);
_ ->
io:format("TOTAL FAIL!~n")
end,
stop_link().
%%%================================================================================
%%% Find solutions functions
%%%================================================================================
find_all(Count, Table, Steps) ->
find_all_step(Count, Table, Steps, fun left/1, 'L', fun find_all/3),
find_all_step(Count, Table, Steps, fun right/1, 'R', fun find_all/3),
case Steps of
[] -> nothing;
_ -> find_all_step(Count, Table, Steps, fun push/1, '+', fun find_all/3)
end.
find_all_step(Count, Table, Steps, Move, Name, Find) ->
case execute(Count, Table, Steps, Move, Name, Find) of
{success, Result} -> add_result(Result);
_ -> nothing
end.
find_first(Count, Table, Steps) ->
case execute(Count, Table, Steps, fun left/1, 'L', fun find_first/3) of
{success, Result} -> {success, Result};
_ ->
case execute(Count, Table, Steps, fun right/1, 'R', fun find_first/3) of
{success, Result} -> {success, Result};
Result ->
case Steps of
[] -> Result;
_ -> execute(Count, Table, Steps, fun push/1, 'P', fun find_first/3)
end
end
end.
execute(Count, Table, Steps, Move, Name, Find) ->
NextTable = Move(Table),
NextSteps = [Name | Steps],
debug("[execute/6] (~w) (~w => ~w)~n~w~n", [Count, Table, NextTable, NextSteps]),
case NextTable =:= ?GOAL of
true ->
{success, lists:reverse(NextSteps)};
_ ->
case Count of
?LIMIT ->
fail;
_ ->
Find(Count+1, NextTable, NextSteps)
end
end.
%%%================================================================================
%%% Game moves functions
%%%================================================================================
left({A, B, C}) -> {A, next(B), next(C)}.
right({A, B, C}) -> {next(A), next(B), C}.
push({A, B, C}) -> {A, next(B), C}.
next(1) -> 2;
next(2) -> 3;
next(3) -> 1.
%%%================================================================================
%%% Utility functions
%%%================================================================================
get_best_solution(Solutions) ->
lists:foldl(
fun({solution, Victim}, Accum) ->
case Accum of
{solution, Current} when length(Current) =< length(Victim) ->
{solution, Current};
_ ->
{solution, Victim}
end
end,
fail, Solutions
).
debug(Message, Args) ->
case ?DEBUG of
true -> io:format(Message, Args);
_ -> ok
end.
%%%================================================================================
%%% Utility functions
%%%================================================================================
add_result(Victim) ->
gen_server:cast(?SERVER, {add_result, Victim}).
get_best_result() ->
gen_server:call(?SERVER, get_best_result).
get_all_results() ->
gen_server:call(?SERVER, get_all_results).
stop_link() ->
gen_server:cast(?SERVER, exterminate).
-spec(start_link() ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%%================================================================================
%%% gen_server callbacks
%%%================================================================================
-spec(init(Args :: term()) ->
{ok, State :: #data_state{}}
| {ok, State :: #data_state{}, timeout() | hibernate}
| {stop, Reason :: term()}
| ignore).
init([]) ->
{ok, #data_state{}}.
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()}, State :: #data_state{}) ->
{reply, Reply :: term(), NewState :: #data_state{}}
| {reply, Reply :: term(), NewState :: #data_state{}, timeout() | hibernate}
| {noreply, NewState :: #data_state{}}
| {noreply, NewState :: #data_state{}, timeout() | hibernate}
| {stop, Reason :: term(), Reply :: term(), NewState :: #data_state{}}
| {stop, Reason :: term(), NewState :: #data_state{}}).
handle_call(get_best_result, _From, State) ->
{reply, get_best_solution(State#data_state.results), State};
handle_call(get_all_results, _From, State) ->
case State#data_state.results of
[] ->
{reply, fail, State};
Results ->
{reply, Results, State}
end;
handle_call(_Request, _From, State) ->
{reply, ok, State}.
-spec(handle_cast(Request :: term(), State :: #data_state{}) ->
{noreply, NewState :: #data_state{}}
| {noreply, NewState :: #data_state{}, timeout() | hibernate}
| {stop, Reason :: term(), NewState :: #data_state{}}).
handle_cast({add_result, {_, Victim}}, State) ->
{noreply, State#data_state{ results = [ {solution, Victim} | State#data_state.results] }};
handle_cast({add_result, Victim}, State) ->
{noreply, State#data_state{ results = [ {solution, Victim} | State#data_state.results] }};
handle_cast(exterminate, State) ->
{stop, normal, State};
handle_cast(_Request, State) ->
{noreply, State}.
-spec(handle_info(Info :: timeout() | term(), State :: #data_state{}) ->
{noreply, NewState :: #data_state{}}
| {noreply, NewState :: #data_state{}, timeout() | hibernate}
| {stop, Reason :: term(), NewState :: #data_state{}}).
handle_info(_Info, State = #data_state{}) ->
{noreply, State}.
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), State :: #data_state{}) -> term()).
terminate(_Reason, _State = #data_state{}) ->
ok.
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #data_state{}, Extra :: term()) ->
{ok, NewState :: #data_state{}}
| {error, Reason :: term()}).
code_change(_OldVsn, State = #data_state{}, _Extra) ->
{ok, State}.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment