2016 Update
This commit is contained in:
parent
948b86eafa
commit
dcf5d15da3
7965 changed files with 139854 additions and 31002 deletions
181
Task/Sokoban/Elixir/sokoban.elixir
Normal file
181
Task/Sokoban/Elixir/sokoban.elixir
Normal 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)
|
||||
138
Task/Sokoban/Java/sokoban.java
Normal file
138
Task/Sokoban/Java/sokoban.java
Normal 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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue