2016 Update

This commit is contained in:
Tina Müller 2016-12-05 22:15:40 +01:00
parent 948b86eafa
commit dcf5d15da3
7965 changed files with 139854 additions and 31002 deletions

View file

@ -0,0 +1,181 @@
defmodule Sokoban do
defp setup(level) do
{leng, board} = normalize(level)
{player, goal} = check_position(board)
board = replace(board, [{".", " "}, {"+", " "}, {"*", "$"}])
lurd = [{-1, "l", "L"}, {-leng, "u", "U"}, {1, "r", "R"}, {leng, "d", "D"}]
dirs = [-1, -leng, 1, leng]
dead_zone = set_dead_zone(board, goal, dirs)
{board, player, goal, lurd, dead_zone}
end
defp normalize(level) do
board = String.split(level, "\n", trim: true)
|> Enum.map(&String.trim_trailing &1)
leng = Enum.map(board, &String.length &1) |> Enum.max
board = Enum.map(board, &String.pad_trailing(&1, leng)) |> Enum.join
{leng, board}
end
defp check_position(board) do
board = String.codepoints(board)
player = Enum.find_index(board, fn c -> c in ["@", "+"] end)
goal = Enum.with_index(board)
|> Enum.filter_map(fn {c,_} -> c in [".", "+", "*"] end, fn {_,i} -> i end)
{player, goal}
end
defp set_dead_zone(board, goal, dirs) do
wall = String.replace(board, ~r/[^#]/, " ")
|> String.codepoints
|> Enum.with_index
|> Enum.into(Map.new, fn {c,i} -> {i,c} end)
corner = search_corner(wall, goal, dirs)
set_dead_zone(wall, dirs, goal, corner, corner)
end
defp set_dead_zone(wall, dirs, goal, corner, dead) do
dead2 = Enum.reduce(corner, dead, fn pos,acc ->
Enum.reduce(dirs, acc, fn dir,acc2 ->
if wall[pos+dir] == "#", do: acc2,
else: acc2 ++ check_side(wall, dirs, pos+dir, dir, goal, dead, [])
end)
end)
if dead == dead2, do: :lists.usort(dead),
else: set_dead_zone(wall, dirs, goal, corner, dead2)
end
defp replace(string, replacement) do
Enum.reduce(replacement, string, fn {a,b},str ->
String.replace(str, a, b)
end)
end
defp search_corner(wall, goal, dirs) do
Enum.reduce(wall, [], fn {i,c},corner ->
if c == "#" or i in goal do
corner
else
case count_wall(wall, i, dirs) do
2 -> if wall[i-1] != wall[i+1], do: [i | corner], else: corner
3 -> [i | corner]
_ -> corner
end
end
end)
end
defp check_side(wall, dirs, pos, dir, goal, dead, acc) do
if wall[pos] == "#" or
count_wall(wall, pos, dirs) == 0 or
pos in goal do
[]
else
if pos in dead, do: acc, else: check_side(wall, dirs, pos+dir, dir, goal, dead, [pos|acc])
end
end
defp count_wall(wall, pos, dirs) do
Enum.count(dirs, fn dir -> wall[pos + dir] == "#" end)
end
defp push_box(board, pos, dir, route, goal, dead_zone) do
pos2dir = pos + 2 * dir
if String.at(board, pos2dir) == " " and not pos2dir in dead_zone do
board2 = board |> replace_at(pos, " ")
|> replace_at(pos+dir, "@")
|> replace_at(pos2dir, "$")
unless visited?(board2) do
if solved?(board2, goal) do
IO.puts route
exit(:normal)
else
queue_in({board2, pos+dir, route})
end
end
end
end
defp move_player(board, pos, dir) do
board |> replace_at(pos, " ") |> replace_at(pos+dir, "@")
end
defp replace_at(str, pos, c) do
{left, right} = String.split_at(str, pos)
{_, right} = String.split_at(right, 1)
left <> c <> right
# String.slice(str, 0, pos) <> c <> String.slice(str, pos+1..-1)
end
defp solved?(board, goal) do
Enum.all?(goal, fn g -> String.at(board, g) == "$" end)
end
@pattern :sokoban_pattern_set
@queue :sokoban_queue
defp start_link do
Agent.start_link(fn -> MapSet.new end, name: @pattern)
Agent.start_link(fn -> :queue.new end, name: @queue)
end
defp visited?(board) do
Agent.get_and_update(@pattern, fn set ->
{board in set, MapSet.put(set, board)}
end)
end
defp queue_in(data) do
Agent.update(@queue, fn queue -> :queue.in(data, queue) end)
end
defp queue_out do
Agent.get_and_update(@queue, fn q ->
case :queue.out(q) do
{{:value, data}, queue} -> {data, queue}
x -> x
end
end)
end
def solve(level) do
{board, player, goal, lurd, dead_zone} = setup(level)
start_link
visited?(board)
queue_in({board, player, ""})
solve(goal, lurd, dead_zone)
end
defp solve(goal, lurd, dead_zone) do
case queue_out do
{board, pos, route} ->
Enum.each(lurd, fn {dir,move,push} ->
case String.at(board, pos+dir) do
"$" -> push_box(board, pos, dir, route<>push, goal, dead_zone)
" " -> board2 = move_player(board, pos, dir)
unless visited?(board2) do
queue_in({board2, pos+dir, route<>move})
end
_ -> :not_move # wall
end
end)
_ ->
IO.puts "No solution"
exit(:normal)
end
solve(goal, lurd, dead_zone)
end
end
level = """
#######
# #
# #
#. # #
#. $$ #
#.$$ #
#.# @#
#######
"""
IO.puts level
Sokoban.solve(level)

View file

@ -0,0 +1,138 @@
import java.util.*;
public class Sokoban {
String destBoard, currBoard;
int playerX, playerY, nCols;
Sokoban(String[] board) {
nCols = board[0].length();
StringBuilder destBuf = new StringBuilder();
StringBuilder currBuf = new StringBuilder();
for (int r = 0; r < board.length; r++) {
for (int c = 0; c < nCols; c++) {
char ch = board[r].charAt(c);
destBuf.append(ch != '$' && ch != '@' ? ch : ' ');
currBuf.append(ch != '.' ? ch : ' ');
if (ch == '@') {
this.playerX = c;
this.playerY = r;
}
}
}
destBoard = destBuf.toString();
currBoard = currBuf.toString();
}
String move(int x, int y, int dx, int dy, String trialBoard) {
int newPlayerPos = (y + dy) * nCols + x + dx;
if (trialBoard.charAt(newPlayerPos) != ' ')
return null;
char[] trial = trialBoard.toCharArray();
trial[y * nCols + x] = ' ';
trial[newPlayerPos] = '@';
return new String(trial);
}
String push(int x, int y, int dx, int dy, String trialBoard) {
int newBoxPos = (y + 2 * dy) * nCols + x + 2 * dx;
if (trialBoard.charAt(newBoxPos) != ' ')
return null;
char[] trial = trialBoard.toCharArray();
trial[y * nCols + x] = ' ';
trial[(y + dy) * nCols + x + dx] = '@';
trial[newBoxPos] = '$';
return new String(trial);
}
boolean isSolved(String trialBoard) {
for (int i = 0; i < trialBoard.length(); i++)
if ((destBoard.charAt(i) == '.')
!= (trialBoard.charAt(i) == '$'))
return false;
return true;
}
String solve() {
class Board {
String cur, sol;
int x, y;
Board(String s1, String s2, int px, int py) {
cur = s1;
sol = s2;
x = px;
y = py;
}
}
char[][] dirLabels = {{'u', 'U'}, {'r', 'R'}, {'d', 'D'}, {'l', 'L'}};
int[][] dirs = {{0, -1}, {1, 0}, {0, 1}, {-1, 0}};
Set<String> history = new HashSet<>();
LinkedList<Board> open = new LinkedList<>();
history.add(currBoard);
open.add(new Board(currBoard, "", playerX, playerY));
while (!open.isEmpty()) {
Board item = open.poll();
String cur = item.cur;
String sol = item.sol;
int x = item.x;
int y = item.y;
for (int i = 0; i < dirs.length; i++) {
String trial = cur;
int dx = dirs[i][0];
int dy = dirs[i][1];
// are we standing next to a box ?
if (trial.charAt((y + dy) * nCols + x + dx) == '$') {
// can we push it ?
if ((trial = push(x, y, dx, dy, trial)) != null) {
// or did we already try this one ?
if (!history.contains(trial)) {
String newSol = sol + dirLabels[i][1];
if (isSolved(trial))
return newSol;
open.add(new Board(trial, newSol, x + dx, y + dy));
history.add(trial);
}
}
// otherwise try changing position
} else if ((trial = move(x, y, dx, dy, trial)) != null) {
if (!history.contains(trial)) {
String newSol = sol + dirLabels[i][0];
open.add(new Board(trial, newSol, x + dx, y + dy));
history.add(trial);
}
}
}
}
return "No solution";
}
public static void main(String[] a) {
String level = "#######,# #,# #,#. # #,#. $$ #,"
+ "#.$$ #,#.# @#,#######";
System.out.println(new Sokoban(level.split(",")).solve());
}
}

View file

@ -2,59 +2,55 @@ require 'set'
class Sokoban
def initialize(level)
board = level.each_line.map(&:chomp)
board = level.each_line.map(&:rstrip)
@nrows = board.map(&:size).max
board = board.map{|line| line.ljust(@nrows)}
board.map!{|line| line.ljust(@nrows)}
board.each_with_index do |row, r|
row.each_char.with_index do |ch, c|
@px, @py = c, r if ch == '@' or ch == '+'
end
end
goal = board.join.tr(' .@#$+*', ' . ..')
@goal = goal.each_char.with_index.select{|ch, c| ch == '.'}.map(&:last)
@data = board.join.tr(' .@#$+*', ' @#$ $')
@goal = board.join.tr(' .@#$+*', ' . ..')
.each_char.with_index.select{|ch, c| ch == '.'}
.map(&:last)
@board = board.join.tr(' .@#$+*', ' @#$ $')
end
def pos(x, y)
y * @nrows + x
end
def push(x, y, dx, dy, data)
return data if data[pos(x+2*dx, y+2*dy)] != ' '
data[pos(x , y )] = ' '
data[pos(x + dx, y + dy)] = '@'
data[pos(x+2*dx, y+2*dy)] = '$'
data
def push(x, y, dx, dy, board) # modify board
return if board[pos(x+2*dx, y+2*dy)] != ' '
board[pos(x , y )] = ' '
board[pos(x + dx, y + dy)] = '@'
board[pos(x+2*dx, y+2*dy)] = '$'
end
def solved?(data)
@goal.all?{|i| data[i] == '$'}
def solved?(board)
@goal.all?{|i| board[i] == '$'}
end
DIRS = [[0, -1, 'u', 'U'], [ 1, 0, 'r', 'R'], [0, 1, 'd', 'D'], [-1, 0, 'l', 'L']]
def solve
open = [[@data, "", @px, @py]]
visited = Set[@data]
queue = [[@board, "", @px, @py]]
visited = Set[@board]
until open.empty?
cur, csol, x, y = open.shift
until queue.empty?
current, csol, x, y = queue.shift
for dx, dy, cmove, cpush in DIRS
temp = cur.dup
ps = pos(x+dx, y+dy)
if temp[ps] == '$'
temp = push(x, y, dx, dy, temp)
next if visited.include?(temp)
visited.add(temp)
return csol + cpush if solved?(temp)
open << [temp, csol + cpush, x+dx, y+dy]
else
next if @data[ps] == '#' or temp[ps] != ' '
temp[pos(x, y)] = ' '
temp[ps] = '@'
next if visited.include?(temp)
visited.add(temp)
open << [temp, csol + cmove, x+dx, y+dy]
work = current.dup
case work[pos(x+dx, y+dy)] # next character
when '$'
next unless push(x, y, dx, dy, work)
next unless visited.add?(work)
return csol+cpush if solved?(work)
queue << [work, csol+cpush, x+dx, y+dy]
when ' '
work[pos(x, y)] = ' '
work[pos(x+dx, y+dy)] = '@'
queue << [work, csol+cmove, x+dx, y+dy] if visited.add?(work)
end
end
end