diff --git a/java/org/artisanlogiciel/games/maze/LabyModel.java b/java/org/artisanlogiciel/games/maze/LabyModel.java index 0a08322..8930dae 100644 --- a/java/org/artisanlogiciel/games/maze/LabyModel.java +++ b/java/org/artisanlogiciel/games/maze/LabyModel.java @@ -557,7 +557,7 @@ public class LabyModel implements WallsProvider { /** * One step in Maze generation process - * get last element in open list and explore one random direction with it. + * get last element in open list and explore one random free direction with it. * * @returns whether process closed current node. **/ @@ -601,6 +601,7 @@ public class LabyModel implements WallsProvider { } if ((newx >= 0) && (newy >= 0) && (newx < width) && (newy < height)) { + // CLEAR is 0 : no tag at all. if ((t[newx][newy]) == CLEAR) { freeDirection.add(Short.valueOf(direction)); } @@ -610,6 +611,7 @@ public class LabyModel implements WallsProvider { if (!freeDirection.isEmpty()) { // control random using our own pseudorandom short direction = 0; + // System.out.println("" + freeDirection.size() + "\n" ); if (freeDirection.size() > 1) { direction = freeDirection.get(random.nextInt(freeDirection.size())); } else { diff --git a/java/org/artisanlogiciel/games/maze/gui/Display.java b/java/org/artisanlogiciel/games/maze/gui/Display.java index 559288e..f81980c 100644 --- a/java/org/artisanlogiciel/games/maze/gui/Display.java +++ b/java/org/artisanlogiciel/games/maze/gui/Display.java @@ -2,20 +2,14 @@ package org.artisanlogiciel.games.maze.gui; import java.awt.BorderLayout; import java.awt.image.BufferedImage; -import java.awt.Color; import java.awt.Container; -import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Graphics; import java.awt.Point; -import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; -import java.awt.event.InputEvent; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; import java.io.*; import java.util.*; @@ -27,11 +21,11 @@ import javax.swing.event.ChangeListener; import org.artisanlogiciel.games.maze.*; import org.artisanlogiciel.games.maze.persist.MazePersistRaw; -import org.artisanlogiciel.games.maze.solve.DirectionPosition; -import org.artisanlogiciel.games.maze.solve.MazeResolutionListener; import org.artisanlogiciel.games.maze.solve.SolvingModel; import org.artisanlogiciel.games.stl.Maze3dParams; import org.artisanlogiciel.games.stl.Wall3dStream; +import org.artisanlogiciel.osm.OsmReader; +import org.artisanlogiciel.osm.convert.OsmToDrawing; import org.artisanlogiciel.util.UTF8Control; import org.artisanlogiciel.graphics.Drawing; @@ -41,7 +35,9 @@ import org.artisanlogiciel.graphics.SvgWriter; /** * Display is Main JFrame for this tool **/ -public class Display extends JFrame { +public class Display extends JFrame +implements StatusListener +{ // to please eclipse, not supposed to be serialized private static final long serialVersionUID = 8500214871372184418L; @@ -92,7 +88,7 @@ public class Display extends JFrame { private MazeComponent createMazeComponent(LabyModel model, int W, int H) { MazeCellParameters cp = new MazeCellParameters(model.getWidth(), model.getHeight(), W, H, 3, 3); - MazeComponent comp = new MazeComponent(model, cp); + MazeComponent comp = new MazeComponent(model, cp, this); return comp; } @@ -332,7 +328,7 @@ public class Display extends JFrame { } - void addStatus(String pStatus) + public void addStatus(String pStatus) { if ( statusEnable ) { statusField.setText(pStatus); @@ -400,6 +396,27 @@ public class Display extends JFrame { loadImcButton.addActionListener(loadImcAction); loadMenu.add(loadImcButton); + + Action loadOsmAction = new AbstractAction() { + public void actionPerformed(ActionEvent evt) { + // + addStatus("load Osm"); + + String filename = loadName.getText(); + + if ((filename.length() > 0)) { + setMazeName(filename); + loadOsm(false); + refresh(); + } + + } + }; + JButton loadOsmButton = new JButton(labels.getString("load" ) + " Osm"); + loadOsmButton.addActionListener(loadOsmAction); + + loadMenu.add(loadOsmButton); + return loadMenu; } @@ -675,6 +692,40 @@ public class Display extends JFrame { } } + private void loadOsm(boolean add) { + if ( maze != null ) { + new Thread() { + @Override + public void run() { + File infile = new File(params.getSaveDir(), params.getName() + ".osm"); + FileInputStream inputStream = null; + try { + // TODO really use InputStream and not pathname + OsmToDrawing converter = new OsmToDrawing(43.6399000, 7.0058300, 10000,10000); + OsmReader reader = new OsmReader(infile.getCanonicalPath()); + reader.read(); + Drawing drawing = converter.getDrawing(reader.getWays()); + statusEnable = false; + maze.addDrawing(drawing,add); + statusEnable = true; + } catch (IOException io) { + io.printStackTrace(System.err); + statusField.setText("[ERROR] Can't load " + infile.getAbsolutePath()); + } finally { + if (inputStream != null) { + // cleanup + try { + inputStream.close(); + } catch (Exception any) { + // don't care really + } + } + } + } + }.start(); + } + } + private void loadImc(boolean add) { if ( maze != null ) { new Thread() { @@ -720,497 +771,6 @@ public class Display extends JFrame { return button; } - private static class MazeCellParameters { - double width = 10; // width of one cell - double height = 10; // height of one cell - int offsetX = 5; // x offset of upper corner left - int offsetY = 5; // y offset of upper corner left - - int mapWidth = 0; - int mapHeight = 0; - - public MazeCellParameters(int mapw, int maph, int W, int H, int x, int y) { - double w = (W - x) / mapw; - double h = (H - y) / maph; - mapWidth = mapw; - mapHeight = maph; - if (w < 5) - w = 5; - if (h < 5) - h = 5; - setCellSize(w, h); - offsetX = x; - offsetY = y; - } - - public void resetMazeWidthHeight(int mapw, int maph) { - mapWidth = mapw; - mapHeight = maph; - } - - public void adaptTo(double W, double H) { - double w = (W - offsetX) / mapWidth; - double h = (H - offsetY) / mapHeight; - mapWidth = mapWidth; - mapHeight = mapHeight; - if (w < 5) - w = 5; - if (h < 5) - h = 5; - setCellSize(w, h); - } - - public void setCellSize(double w, double h) { - width = w; - height = h; - } - - public double getWidth() { - return width; - } - - public double getHeight() { - return height; - } - - public int getOffsetX() { - return offsetX; - } - - public int getOffsetY() { - return offsetY; - } - - public Dimension getDimension() { - return new Dimension(offsetX + (int) (mapWidth * width), - offsetY + (int) (mapHeight * height)); - } - - public void drawWalls(Graphics g, int pX, int pY, short walls) { - int x = offsetX + (int) (pX * width); - int y = offsetY + (int) (pY * height); - if ((pY == 0) && ((walls & Brick.UP) == Brick.UP)) - g.drawLine(x, y, x + (int) width, y); - if ((walls & Brick.DOWN) == Brick.DOWN) - g.drawLine(x, y + (int) height, x + (int) width, y + (int) height); - if ((walls & Brick.RIGHT) == Brick.RIGHT) - g.drawLine(x + (int) width, y, x + (int) width, y + (int) height); - if ((pX == 0) && ((walls & Brick.LEFT) == Brick.LEFT)) - g.drawLine(x, y, x, y + (int) height); - } - - public void drawPath(Graphics g, DirectionPosition dp, int pX, int pY, int mX, int mY) { - if (dp != null) { - Position dot = dp.getPosition(); - if ((dot.getX() >= pX) && (dot.getY() >= pY) && (dot.getX() < mX) && (dot.getY() < mY)) { - int x = offsetX + (int) (dot.getX() * width); - int y = offsetY + (int) (dot.getY() * height); - short path = dp.getDirection(); - int xm = x + (int) (width / 2); - int ym = y + (int) (height / 2); - if ((path & LabyModel.HORIZONTAL) == LabyModel.HORIZONTAL) { - if ((path & LabyModel.POSITIVE) == LabyModel.POSITIVE) { - g.drawLine(xm, ym, x + (int) width, ym); - g.drawLine(xm, ym + (int) (height / 4), x + (int) width, ym); - } - // LEFT /_ - if ((path & LabyModel.NEGATIVE) == LabyModel.NEGATIVE) { - g.drawLine(x, ym, xm, ym); - g.drawLine(x, ym, xm, ym - (int) (height / 4)); - } - } - if ((path & LabyModel.VERTICAL) == LabyModel.VERTICAL) { - if ((path & LabyModel.POSITIVE) == LabyModel.POSITIVE) { - g.drawLine(xm, ym, xm, y + (int) height); - g.drawLine(xm - (int) (width / 4), ym, xm, y + (int) height); - } - // UP |\ - if ((path & LabyModel.NEGATIVE) == LabyModel.NEGATIVE) { - g.drawLine(xm, ym, xm, y); - g.drawLine(xm + (int) (width / 4), ym, xm, y); - } - } - } - } - } - - public void drawDot(Graphics g, Position dot, int pX, int pY, int mX, int mY) { - double radius = (height > width) ? width : height; - int a = (int) (radius / 4); - if ((dot.getX() >= pX) && (dot.getY() >= pY) && (dot.getX() < mX) && (dot.getY() < mY)) { - int x = offsetX + (int) (dot.getX() * width); - int y = offsetY + (int) (dot.getY() * height); - int r2 = (int) ((radius * 3) / 4); - g.drawOval(x + 1, y + 1, r2, r2); - // g.drawLine(x+a,y+a,x+width-a,y+height-a); - // g.drawLine(x+a,y+height-a,x+width-a,y+a); - - } else { - int x = offsetX + (int) (pX * width); - int y = offsetY + (int) (pY * height); - g.drawLine(x + 1, y + 1, x + (int) width - 1, y + (int) height - 1); - } - } - - } - - private class MazeComponent - extends JComponent - implements MazeCreationListener, - MazeResolutionListener, - MouseMotionListener { - private static final long serialVersionUID = 3163272907991176390L; - - // WallsProvider map; - LabyModel map; - final MazeCellParameters cp; - Position current = null; - LinkedList solvedPath = null; - LinkedList drawingPath = null; - final Object lockChange = new Object(); - // current postion of human resolving - int sX = 0; - int sY = 0; - // goal exit - int gX = -1; - int gY = -1; - - Date lastDrag = null; - // FIXME HARCDODED delay after which a draging to draw a continous line is ended, a new line will then start. - long dragTimeout = 200; - - // not set by default for debug, will show any path - boolean showAll = false; - - // for a given (x,y) pixel return cell position. - // if rightmost position of view not (0,0) this is not working. - Position getPosition(int x, int y) { - int pX = (int) ((double) (x - cp.getOffsetX()) / cp.getWidth()); - int pY = (int) ((double) (y - cp.getOffsetY()) / cp.getHeight()); - return new Position(pX, pY); - } - - @Override - public void mouseDragged(MouseEvent e) { - boolean add = ((e.getModifiersEx() & InputEvent.BUTTON1_DOWN_MASK) != 0); - Position newPosition = getPosition(e.getX(), e.getY()); - Date now = new Date(System.currentTimeMillis()); - if (lastDrag == null ) - { - resetDrawingPath(); - } - else - { - if ( now.getTime() - lastDrag.getTime() > dragTimeout ) - { - resetDrawingPath(); - addStatus("move timeout"); - } - } - lastDrag = now; - addPosition(newPosition,add); - changed(null, null, map); - } - - public void resetDrawingPath() - { - drawingPath = null; - } - - public void addDrawing(Drawing drawing, boolean add) - { - for (DrawingLine line : drawing.getInternLines()) - { - addDrawingLine(line, add); - } - changed(null, null, map); - } - public void addDrawingLine(DrawingLine line, boolean add) - { - resetDrawingPath(); - ArrayList points = line.getPoints(); - for (Point p : points) - { - addPosition(new Position(p.x,p.y), add); - } - } - - public void addPosition(Position newPosition, boolean add ) { - // should find the cell ... - DirectionPosition last = null; - short path = 0; - if (drawingPath == null) { - drawingPath = new LinkedList<>(); - last = new DirectionPosition((short) 0, newPosition); - if ( statusEnable) { - addStatus("Mouse dragged Cell " + newPosition + " Button " + add); - } - drawingPath.addLast(last); - } else { - // setShowAll(true); - DirectionPosition first = drawingPath.getLast(); - last = first; - // construct as many move form last to new position as needed. - while (!last.getPosition().equals(newPosition)) { - path = LabyModel.getDirection(last.getPosition(), newPosition); - last.setDirection(path); - // button 1 : add direction; button 2 : rep lace with direction. - if (add) { - map.addDirection(last.getPosition().getX(), last.getPosition().getY(), path); - } else { - map.setDirection(last.getPosition().getX(), last.getPosition().getY(), path); - } - last = last.moveToAdjacentDirection(); - drawingPath.addLast(last); - } - if (statusEnable) { - addStatus("Mouse dragged from Cell " + first.getPosition() + "To" + newPosition + " Button " + add); - } - } - } - - @Override - public void mouseMoved(MouseEvent e) { - // addStatus("Mouse moved (" + e.getX() + ',' + e.getY() + ')'); - Position newPosition = getPosition(e.getX(), e.getY()); - if ((newPosition.getX() >= 0) && (newPosition.getY() >= 0)) { - requestFocus(); - } - } - - public void setShowAll(boolean showall) { - this.showAll = showall; - addStatus(showAll ? "show all" : "X"); - } - - void checkExit() { - if ((sX == gX) && (sY == gY)) { - addStatus("Exit found by human !"); - } - } - - private void proceed() - { - // should redraw ... - invalidate(); - repaint(); - checkExit(); - } - void goNorth() { - resetResolution(); - if (map.canMoveInDirection(sX, sY,Brick.UP)) { - sY = sY - 1; - proceed(); - } - } - - void goSouth() { - resetResolution(); - if (map.canMoveInDirection(sX, sY,Brick.DOWN)) { - sY = sY + 1; - proceed(); - } - } - - void goEast() { - resetResolution(); - if (map.canMoveInDirection(sX, sY, Brick.RIGHT)) { - sX = sX + 1; - proceed(); - } - } - - void goWest() { - resetResolution(); - if (map.canMoveInDirection(sX, sY, Brick.LEFT)) { - sX = sX - 1; - proceed(); - } - } - - /** - * width, height of one cell, - * offsetX, offsetY of upper left corner - **/ - // public MazeComponent(WallsProvider map, MazeCellParameters cp) - public MazeComponent(LabyModel map, MazeCellParameters cp) { - super(); - this.cp = cp; - this.map = map; - setPreferredSize(cp.getDimension()); - gX = map.getWidth() - 1; - gY = map.getHeight() - 1; - } - - // public void resetWallsProvider(WallsProvider map) - public void resetWallsProvider(LabyModel map) { - this.map = map; - solvedPath = null; - // could be kept - drawingPath = null; - sX = 0; - sY = 0; - cp.resetMazeWidthHeight(map.getWidth(), map.getHeight()); - setPreferredSize(cp.getDimension()); - } - - public void setWallSize(int size) { - cp.setCellSize((double) size, (double) size); - // should redraw ... - invalidate(); - repaint(); - } - - public int getAutoSize() { - Rectangle r = getBounds(); - cp.adaptTo((int) r.getWidth(), (int) r.getHeight()); - // should redraw ... - invalidate(); - repaint(); - return 50; - } - - public void changed(Position cl, Position cr, WallsProvider map) { - // should redraw ... - invalidate(); - repaint(); - Object waiter = new Object(); - synchronized (waiter) { - try { - waiter.wait(10); - } catch (InterruptedException e) { - System.err.println("Interrupted !"); - } - } - } - - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - int x = 0; - int y = 0; - short walls = 0; - short path = 0; - - // try to display only visible part... - // should compute pX, pY, mX, mY based on clip... - // Shape s=g.getClip(); - Rectangle r = g.getClipBounds(); - - int mX = (int) ((double) r.getWidth() / cp.getWidth()); - int mY = (int) ((double) r.getHeight() / cp.getHeight()); - int pX = (int) ((double) (r.getX() - cp.getOffsetX()) / cp.getWidth()); - int pY = (int) ((double) (r.getY() - cp.getOffsetY()) / cp.getHeight()); - - if (pX < 0) - pX = 0; - if (pY < 0) - pY = 0; - if (pX >= map.getWidth()) - return; - if (pY >= map.getHeight()) - return; - - mX = mX + pX; - mY = mY + pY; - - if (mX > map.getWidth()) { - mX = map.getWidth(); - } - if (mY > map.getHeight()) { - mY = map.getHeight(); - } - - if ((sX == gX) && (sY == gY)) { - g.setColor(Color.red); - } else { - g.setColor(Color.green); - cp.drawDot(g, new Position(gX, gY), pX, pY, mX, mY); - g.setColor(Color.blue); - } - cp.drawDot(g, new Position(sX, sY), pX, pY, mX, mY); - - synchronized (lockChange) { - g.setColor(Color.red); - if (current != null) { - cp.drawDot(g, current, pX, pY, mX, mY); - } - if (solvedPath != null) { - for (DirectionPosition resolved : solvedPath) { - // cp.drawDot(g, resolved.getPosition(), pX, pY, mX, mY); - cp.drawPath(g, resolved, pX, pY, mX, mY); - } - } - } - - int aX = pX; - int aY = pY; - - g.setColor(Color.black); - - // draw all walls within clip bounds horiz first then lines - for (; pY < mY; pY++) { - for (pX = 0; pX < mX; pX++) { - walls = map.getWalls(pX, pY); - cp.drawWalls(g, pX, pY, walls); - } - } - - if (this.showAll) { - addStatus("*"); - pX = aX; - pY = aY; - - // draw all path within clip bounds horiz first then lines - for (; pY < mY; pY++) { - for (pX = 0; pX < mX; pX++) { - path = map.getPath(pX, pY); - if ((path & LabyModel.SOLVED) == LabyModel.SOLVED) { - g.setColor(Color.green); - } else { - g.setColor(Color.blue); - } - if (path != 0) { - DirectionPosition dp = new DirectionPosition(path, new Position(pX, pY)); - cp.drawPath(g, dp, pX, pY, pX + 1, pY + 1); - } - } - } - } - } - - - public boolean notifySearch(DirectionPosition pPosition) { - synchronized (lockChange) { - current = pPosition.getPosition(); - } - // should redraw ... - invalidate(); - repaint(); - return true; - - } - - public void notifySearchError(String error) { - System.err.println(error); - } - - public void notifyCompletion(LinkedList solvedPath) { - LinkedList newPath = new LinkedList<>(solvedPath); - addStatus("resolution completed"); - synchronized (lockChange) { - this.solvedPath = newPath; - } - // should redraw ... - invalidate(); - repaint(); - } - - public void resetResolution() { - solvedPath = null; - } - } - private static void setupDisplay(LabyModel model, int W, int H, MazeParams params) { Display display = new Display(model, W, H, params); } diff --git a/java/org/artisanlogiciel/games/maze/gui/MazeCellParameters.java b/java/org/artisanlogiciel/games/maze/gui/MazeCellParameters.java new file mode 100644 index 0000000..83cb209 --- /dev/null +++ b/java/org/artisanlogiciel/games/maze/gui/MazeCellParameters.java @@ -0,0 +1,142 @@ +package org.artisanlogiciel.games.maze.gui; + +import org.artisanlogiciel.games.maze.Brick; +import org.artisanlogiciel.games.maze.LabyModel; +import org.artisanlogiciel.games.maze.Position; +import org.artisanlogiciel.games.maze.solve.DirectionPosition; + +import java.awt.*; + +public class MazeCellParameters { + double width = 10; // width of one cell + double height = 10; // height of one cell + int offsetX = 5; // x offset of upper corner left + int offsetY = 5; // y offset of upper corner left + + int mapWidth = 0; + int mapHeight = 0; + + public MazeCellParameters(int mapw, int maph, int W, int H, int x, int y) { + double w = (W - x) / mapw; + double h = (H - y) / maph; + mapWidth = mapw; + mapHeight = maph; + if (w < 5) + w = 5; + if (h < 5) + h = 5; + setCellSize(w, h); + offsetX = x; + offsetY = y; + } + + public void resetMazeWidthHeight(int mapw, int maph) { + mapWidth = mapw; + mapHeight = maph; + } + + public void adaptTo(double W, double H) { + double w = (W - offsetX) / mapWidth; + double h = (H - offsetY) / mapHeight; + mapWidth = mapWidth; + mapHeight = mapHeight; + if (w < 5) + w = 5; + if (h < 5) + h = 5; + setCellSize(w, h); + } + + public void setCellSize(double w, double h) { + width = w; + height = h; + } + + public double getWidth() { + return width; + } + + public double getHeight() { + return height; + } + + public int getOffsetX() { + return offsetX; + } + + public int getOffsetY() { + return offsetY; + } + + public Dimension getDimension() { + return new Dimension(offsetX + (int) (mapWidth * width), + offsetY + (int) (mapHeight * height)); + } + + public void drawWalls(Graphics g, int pX, int pY, short walls) { + int x = offsetX + (int) (pX * width); + int y = offsetY + (int) (pY * height); + if ((pY == 0) && ((walls & Brick.UP) == Brick.UP)) + g.drawLine(x, y, x + (int) width, y); + if ((walls & Brick.DOWN) == Brick.DOWN) + g.drawLine(x, y + (int) height, x + (int) width, y + (int) height); + if ((walls & Brick.RIGHT) == Brick.RIGHT) + g.drawLine(x + (int) width, y, x + (int) width, y + (int) height); + if ((pX == 0) && ((walls & Brick.LEFT) == Brick.LEFT)) + g.drawLine(x, y, x, y + (int) height); + } + + public void drawPath(Graphics g, DirectionPosition dp, int pX, int pY, int mX, int mY) { + if (dp != null) { + Position dot = dp.getPosition(); + if ((dot.getX() >= pX) && (dot.getY() >= pY) && (dot.getX() < mX) && (dot.getY() < mY)) { + int x = offsetX + (int) (dot.getX() * width); + int y = offsetY + (int) (dot.getY() * height); + short path = dp.getDirection(); + int xm = x + (int) (width / 2); + int ym = y + (int) (height / 2); + if ((path & LabyModel.HORIZONTAL) == LabyModel.HORIZONTAL) { + if ((path & LabyModel.POSITIVE) == LabyModel.POSITIVE) { + g.drawLine(xm, ym, x + (int) width, ym); + g.drawLine(xm, ym + (int) (height / 4), x + (int) width, ym); + } + // LEFT /_ + if ((path & LabyModel.NEGATIVE) == LabyModel.NEGATIVE) { + g.drawLine(x, ym, xm, ym); + g.drawLine(x, ym, xm, ym - (int) (height / 4)); + } + } + if ((path & LabyModel.VERTICAL) == LabyModel.VERTICAL) { + if ((path & LabyModel.POSITIVE) == LabyModel.POSITIVE) { + g.drawLine(xm, ym, xm, y + (int) height); + g.drawLine(xm - (int) (width / 4), ym, xm, y + (int) height); + } + // UP |\ + if ((path & LabyModel.NEGATIVE) == LabyModel.NEGATIVE) { + g.drawLine(xm, ym, xm, y); + g.drawLine(xm + (int) (width / 4), ym, xm, y); + } + } + } + } + } + + public void drawDot(Graphics g, Position dot, int pX, int pY, int mX, int mY) { + double radius = (height > width) ? width : height; + int a = (int) (radius / 4); + if ((dot.getX() >= pX) && (dot.getY() >= pY) && (dot.getX() < mX) && (dot.getY() < mY)) { + int x = offsetX + (int) (dot.getX() * width); + int y = offsetY + (int) (dot.getY() * height); + int r2 = (int) ((radius * 3) / 4); + g.drawOval(x + 1, y + 1, r2, r2); + // g.drawLine(x+a,y+a,x+width-a,y+height-a); + // g.drawLine(x+a,y+height-a,x+width-a,y+a); + + } else { + int x = offsetX + (int) (pX * width); + int y = offsetY + (int) (pY * height); + g.drawLine(x + 1, y + 1, x + (int) width - 1, y + (int) height - 1); + } + } + +} diff --git a/java/org/artisanlogiciel/games/maze/gui/MazeComponent.java b/java/org/artisanlogiciel/games/maze/gui/MazeComponent.java new file mode 100644 index 0000000..62b85ac --- /dev/null +++ b/java/org/artisanlogiciel/games/maze/gui/MazeComponent.java @@ -0,0 +1,367 @@ +package org.artisanlogiciel.games.maze.gui; + +import org.artisanlogiciel.games.maze.*; +import org.artisanlogiciel.games.maze.solve.DirectionPosition; +import org.artisanlogiciel.games.maze.solve.MazeResolutionListener; +import org.artisanlogiciel.graphics.Drawing; +import org.artisanlogiciel.graphics.DrawingLine; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.InputEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionListener; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedList; + +public class MazeComponent + extends JComponent + implements MazeCreationListener, + MazeResolutionListener, + MouseMotionListener { + private static final long serialVersionUID = 3163272907991176390L; + + // WallsProvider map; + LabyModel map; + final MazeCellParameters cp; + Position current = null; + LinkedList solvedPath = null; + LinkedList drawingPath = null; + final Object lockChange = new Object(); + // current postion of human resolving + int sX = 0; + int sY = 0; + // goal exit + int gX = -1; + int gY = -1; + + Date lastDrag = null; + // FIXME HARCDODED delay after which a draging to draw a continous line is ended, a new line will then start. + long dragTimeout = 200; + + // not set by default for debug, will show any path + boolean showAll = false; + + StatusListener statusListener; + + // for a given (x,y) pixel return cell position. + // if rightmost position of view not (0,0) this is not working. + Position getPosition(int x, int y) { + int pX = (int) ((double) (x - cp.getOffsetX()) / cp.getWidth()); + int pY = (int) ((double) (y - cp.getOffsetY()) / cp.getHeight()); + return new Position(pX, pY); + } + + @Override + public void mouseDragged(MouseEvent e) { + boolean add = ((e.getModifiersEx() & InputEvent.BUTTON1_DOWN_MASK) != 0); + Position newPosition = getPosition(e.getX(), e.getY()); + Date now = new Date(System.currentTimeMillis()); + if (lastDrag == null) { + resetDrawingPath(); + } else { + if (now.getTime() - lastDrag.getTime() > dragTimeout) { + resetDrawingPath(); + statusListener.addStatus("move timeout"); + } + } + lastDrag = now; + addPosition(newPosition, add); + changed(null, null, map); + } + + public void resetDrawingPath() { + drawingPath = null; + } + + public void addDrawing(Drawing drawing, boolean add) { + for (DrawingLine line : drawing.getInternLines()) { + addDrawingLine(line, add); + } + changed(null, null, map); + } + + public void addDrawingLine(DrawingLine line, boolean add) { + resetDrawingPath(); + ArrayList points = line.getPoints(); + for (Point p : points) { + addPosition(new Position(p.x, p.y), add); + } + } + + public void addPosition(Position newPosition, boolean add) { + // should find the cell ... + DirectionPosition last = null; + short path = 0; + if (drawingPath == null) { + drawingPath = new LinkedList<>(); + last = new DirectionPosition((short) 0, newPosition); + statusListener.addStatus("Mouse dragged Cell " + newPosition + " Button " + add); + drawingPath.addLast(last); + } else { + // setShowAll(true); + DirectionPosition first = drawingPath.getLast(); + last = first; + // construct as many move form last to new position as needed. + while (!last.getPosition().equals(newPosition)) { + path = LabyModel.getDirection(last.getPosition(), newPosition); + last.setDirection(path); + // button 1 : add direction; button 2 : rep lace with direction. + if (add) { + map.addDirection(last.getPosition().getX(), last.getPosition().getY(), path); + } else { + map.setDirection(last.getPosition().getX(), last.getPosition().getY(), path); + } + last = last.moveToAdjacentDirection(); + drawingPath.addLast(last); + } + statusListener.addStatus("Mouse dragged from Cell " + first.getPosition() + "To" + newPosition + " Button " + add); + } + } + + @Override + public void mouseMoved(MouseEvent e) { + // addStatus("Mouse moved (" + e.getX() + ',' + e.getY() + ')'); + Position newPosition = getPosition(e.getX(), e.getY()); + if ((newPosition.getX() >= 0) && (newPosition.getY() >= 0)) { + requestFocus(); + } + } + + public void setShowAll(boolean showall) { + this.showAll = showall; + statusListener.addStatus(showAll ? "show all" : "X"); + } + + void checkExit() { + if ((sX == gX) && (sY == gY)) { + statusListener.addStatus("Exit found by human !"); + } + } + + private void proceed() { + // should redraw ... + invalidate(); + repaint(); + checkExit(); + } + + void goNorth() { + resetResolution(); + if (map.canMoveInDirection(sX, sY, Brick.UP)) { + sY = sY - 1; + proceed(); + } + } + + void goSouth() { + resetResolution(); + if (map.canMoveInDirection(sX, sY, Brick.DOWN)) { + sY = sY + 1; + proceed(); + } + } + + void goEast() { + resetResolution(); + if (map.canMoveInDirection(sX, sY, Brick.RIGHT)) { + sX = sX + 1; + proceed(); + } + } + + void goWest() { + resetResolution(); + if (map.canMoveInDirection(sX, sY, Brick.LEFT)) { + sX = sX - 1; + proceed(); + } + } + + /** + * width, height of one cell, + * offsetX, offsetY of upper left corner + **/ + // public MazeComponent(WallsProvider map, MazeCellParameters cp) + public MazeComponent(LabyModel map, MazeCellParameters cp, StatusListener statusListener) { + super(); + this.cp = cp; + this.map = map; + setPreferredSize(cp.getDimension()); + gX = map.getWidth() - 1; + gY = map.getHeight() - 1; + this.statusListener = statusListener; + } + + // public void resetWallsProvider(WallsProvider map) + public void resetWallsProvider(LabyModel map) { + this.map = map; + solvedPath = null; + // could be kept + drawingPath = null; + sX = 0; + sY = 0; + cp.resetMazeWidthHeight(map.getWidth(), map.getHeight()); + setPreferredSize(cp.getDimension()); + } + + public void setWallSize(int size) { + cp.setCellSize((double) size, (double) size); + // should redraw ... + invalidate(); + repaint(); + } + + public int getAutoSize() { + Rectangle r = getBounds(); + cp.adaptTo((int) r.getWidth(), (int) r.getHeight()); + // should redraw ... + invalidate(); + repaint(); + return 50; + } + + public void changed(Position cl, Position cr, WallsProvider map) { + // should redraw ... + invalidate(); + repaint(); + /* what was this ? + + Object waiter = new Object(); + synchronized (waiter) { + try { + waiter.wait(10); + } catch (InterruptedException e) { + System.err.println("Interrupted !"); + } + } + */ + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + int x = 0; + int y = 0; + short walls = 0; + short path = 0; + + // try to display only visible part... + // should compute pX, pY, mX, mY based on clip... + // Shape s=g.getClip(); + Rectangle r = g.getClipBounds(); + + int mX = (int) ((double) r.getWidth() / cp.getWidth()); + int mY = (int) ((double) r.getHeight() / cp.getHeight()); + int pX = (int) ((double) (r.getX() - cp.getOffsetX()) / cp.getWidth()); + int pY = (int) ((double) (r.getY() - cp.getOffsetY()) / cp.getHeight()); + + if (pX < 0) + pX = 0; + if (pY < 0) + pY = 0; + if (pX >= map.getWidth()) + return; + if (pY >= map.getHeight()) + return; + + mX = mX + pX; + mY = mY + pY; + + if (mX > map.getWidth()) { + mX = map.getWidth(); + } + if (mY > map.getHeight()) { + mY = map.getHeight(); + } + + if ((sX == gX) && (sY == gY)) { + g.setColor(Color.red); + } else { + g.setColor(Color.green); + cp.drawDot(g, new Position(gX, gY), pX, pY, mX, mY); + g.setColor(Color.blue); + } + cp.drawDot(g, new Position(sX, sY), pX, pY, mX, mY); + + synchronized (lockChange) { + g.setColor(Color.red); + if (current != null) { + cp.drawDot(g, current, pX, pY, mX, mY); + } + if (solvedPath != null) { + for (DirectionPosition resolved : solvedPath) { + // cp.drawDot(g, resolved.getPosition(), pX, pY, mX, mY); + cp.drawPath(g, resolved, pX, pY, mX, mY); + } + } + } + + int aX = pX; + int aY = pY; + + g.setColor(Color.black); + + // draw all walls within clip bounds horiz first then lines + for (; pY < mY; pY++) { + for (pX = 0; pX < mX; pX++) { + walls = map.getWalls(pX, pY); + cp.drawWalls(g, pX, pY, walls); + } + } + + if (this.showAll) { + statusListener.addStatus("*"); + pX = aX; + pY = aY; + + // draw all path within clip bounds horiz first then lines + for (; pY < mY; pY++) { + for (pX = 0; pX < mX; pX++) { + path = map.getPath(pX, pY); + if ((path & LabyModel.SOLVED) == LabyModel.SOLVED) { + g.setColor(Color.green); + } else { + g.setColor(Color.blue); + } + if (path != 0) { + DirectionPosition dp = new DirectionPosition(path, new Position(pX, pY)); + cp.drawPath(g, dp, pX, pY, pX + 1, pY + 1); + } + } + } + } + } + + + public boolean notifySearch(DirectionPosition pPosition) { + synchronized (lockChange) { + current = pPosition.getPosition(); + } + // should redraw ... + invalidate(); + repaint(); + return true; + + } + + public void notifySearchError(String error) { + System.err.println(error); + } + + public void notifyCompletion(LinkedList solvedPath) { + LinkedList newPath = new LinkedList<>(solvedPath); + statusListener.addStatus("resolution completed"); + synchronized (lockChange) { + this.solvedPath = newPath; + } + // should redraw ... + invalidate(); + repaint(); + } + + public void resetResolution() { + solvedPath = null; + } +} diff --git a/java/org/artisanlogiciel/games/maze/gui/StatusListener.java b/java/org/artisanlogiciel/games/maze/gui/StatusListener.java new file mode 100644 index 0000000..b6bf2f6 --- /dev/null +++ b/java/org/artisanlogiciel/games/maze/gui/StatusListener.java @@ -0,0 +1,7 @@ +package org.artisanlogiciel.games.maze.gui; + +public interface StatusListener { + + void addStatus(String pStatus); + +} diff --git a/java/org/artisanlogiciel/games/minetest/Node.java b/java/org/artisanlogiciel/games/minetest/Node.java new file mode 100644 index 0000000..92d6d7f --- /dev/null +++ b/java/org/artisanlogiciel/games/minetest/Node.java @@ -0,0 +1,2 @@ +package org.artisanlogiciel.games.minetest;public class Node { +} diff --git a/java/org/artisanlogiciel/osm/Node.java b/java/org/artisanlogiciel/osm/Node.java new file mode 100644 index 0000000..2b3993d --- /dev/null +++ b/java/org/artisanlogiciel/osm/Node.java @@ -0,0 +1,34 @@ +package org.artisanlogiciel.osm; + +/** + * https://wiki.openstreetmap.org/wiki/Node + */ +public class Node { + NodeRef ref; + double lat; + double lon; + + public NodeRef getRef() { + return ref; + } + + public Node(NodeRef ref, double lat, double lon) { + this.ref = ref; + ref.setNode(this); + this.lat = lat; + this.lon = lon; + } + + public double getLat() { + return lat; + } + + public double getLon() { + return lon; + } + + public String toString() + { + return "(id=" + ref.id + " lon=" + lon + " lat=" + lat + ")"; + } +} diff --git a/java/org/artisanlogiciel/osm/NodeRef.java b/java/org/artisanlogiciel/osm/NodeRef.java new file mode 100644 index 0000000..3008e6e --- /dev/null +++ b/java/org/artisanlogiciel/osm/NodeRef.java @@ -0,0 +1,37 @@ +package org.artisanlogiciel.osm; + +public class NodeRef { + long id; + Node node; + + public NodeRef(long id) { + this.id = id; + } + + public void setNode(Node node) { + this.node = node; + } + + public Node getNode() + { + return node; + } + + public String toString() + { + return node != null ? node.toString(): "nd:" + id; + } + + @Override + public int hashCode() { + return Long.hashCode(id); + } + + @Override + public boolean equals(Object o) { + if ( o instanceof NodeRef ) { + return ((NodeRef) o).id == id; + } + return false; + } +} diff --git a/java/org/artisanlogiciel/osm/OsmReader.java b/java/org/artisanlogiciel/osm/OsmReader.java new file mode 100644 index 0000000..70c761d --- /dev/null +++ b/java/org/artisanlogiciel/osm/OsmReader.java @@ -0,0 +1,109 @@ +package org.artisanlogiciel.osm; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.FileInputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class OsmReader { + + private String sample; + + private HashMap refs; + private List ways; + + public OsmReader(String sample) { + this.sample = sample; + refs = new HashMap<>(); + ways = new ArrayList<>(); + } + + public List getWays() { + return ways; + } + + public void read() + { + /* TODO get minlat and minlon **/ + DocumentBuilderFactory factory = + DocumentBuilderFactory.newInstance(); + try { + DocumentBuilder builder = factory.newDocumentBuilder(); + Document doc = builder.parse(new FileInputStream(sample)); + Element root = doc.getDocumentElement(); + System.out.println(root); + NodeList nList = doc.getElementsByTagName("node"); + for (int temp = 0; temp < nList.getLength(); temp++) { + org.w3c.dom.Node nNode = nList.item(temp); + if (nNode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) { + Element e = (Element) nNode; + long id = Long.valueOf(e.getAttribute("id")); + double lat = Double.valueOf(e.getAttribute("lat")); + double lon = Double.valueOf(e.getAttribute("lon")); + Node node = new Node(new NodeRef(id), lat, lon); + refs.put(node.getRef(),node); + } + } + NodeList wList = doc.getElementsByTagName("way"); + for (int temp = 0; temp < wList.getLength(); temp++) { + org.w3c.dom.Node wNode = wList.item(temp); + if (wNode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) { + Element e = (Element) wNode; + try { + long id = Long.valueOf(e.getAttribute("id")); + List nodeRefList = new ArrayList<>(); + NodeList nrList = e.getElementsByTagName("nd"); + for (int temp2 = 0; temp2 < nrList.getLength(); temp2++) { + org.w3c.dom.Node nrNode = nrList.item(temp2); + if (nrNode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) { + Element nd = (Element) nrNode; + long ref = Long.valueOf(nd.getAttribute("ref")); + NodeRef nodeRef = new NodeRef(ref); + Node node = refs.get(nodeRef); + if ( node == null ) + { + System.out.println("unknown node id " + nodeRef); + } + else { + nodeRefList.add(node.getRef()); + } + } + } + Way way = new Way(id, nodeRefList); + ways.add(way); + } + catch (Exception bad) + { + System.err.println("Too bad way"); + bad.printStackTrace(System.err); + } + } + } + } + catch(Exception any) + { + System.err.println("Too bad"); + any.printStackTrace(System.err); + } + } + + public void dump() + { + System.out.println(refs); + System.out.println(ways); + } + public static void main(String pArgs[]) + { + String sample = "/home/plhardy/valbonne3D/valbonne_oct_2020.osm"; + + OsmReader osmreader = new OsmReader(sample); + osmreader.read(); + osmreader.dump(); + } +} diff --git a/java/org/artisanlogiciel/osm/Way.java b/java/org/artisanlogiciel/osm/Way.java new file mode 100644 index 0000000..0017077 --- /dev/null +++ b/java/org/artisanlogiciel/osm/Way.java @@ -0,0 +1,22 @@ +package org.artisanlogiciel.osm; + +import java.util.List; + +public class Way { + long id; + + public Way(long id) { + this.id = id; + } + + public Way(long id, List ndlist) { + this.id = id; + this.ndlist = ndlist; + } + + public List getNdlist() { + return ndlist; + } + + List ndlist; +} diff --git a/java/org/artisanlogiciel/osm/convert/OsmToDrawing.java b/java/org/artisanlogiciel/osm/convert/OsmToDrawing.java new file mode 100644 index 0000000..fefbf5b --- /dev/null +++ b/java/org/artisanlogiciel/osm/convert/OsmToDrawing.java @@ -0,0 +1,59 @@ +package org.artisanlogiciel.osm.convert; + +import org.artisanlogiciel.graphics.Drawing; +import org.artisanlogiciel.graphics.DrawingLine; +import org.artisanlogiciel.osm.Node; +import org.artisanlogiciel.osm.NodeRef; +import org.artisanlogiciel.osm.Way; + +import java.awt.*; +import java.util.List; + +/** + * Take a way with resolved ref ( ie with NodeRef having non null getNode() ) + * and create a Drawing from it. + */ +public class OsmToDrawing { + + double refx = 0; + double refy = 0; + int mulx = 1; + int muly = 1; + + public OsmToDrawing() + { + + } + + public OsmToDrawing(int mulx, int muly) { + this.mulx = mulx; + this.muly = muly; + } + + public OsmToDrawing(double refx, double refy, int mulx, int muly) { + this.refx = refx; + this.refy = refy; + this.mulx = mulx; + this.muly = muly; + } + + public DrawingLine getDrawingLine(Way way) + { + DrawingLine drawingLine = new DrawingLine(); + for (NodeRef nr : way.getNdlist()) + { + Node node = nr.getNode(); + drawingLine.addPoint(new Point((int) ( (node.getLat() - refx)* mulx ) , (int) ( (node.getLon() - refy ) * muly) )); + } + return drawingLine; + } + + public Drawing getDrawing(List ways) + { + Drawing drawing = new Drawing(); + for (Way way : ways) { + drawing.addLine(getDrawingLine(way)); + } + return drawing; + } +} diff --git a/lab/valbonne_oct_2020.osm b/lab/valbonne_oct_2020.osm new file mode 100644 index 0000000..125d48c --- /dev/null +++ b/lab/valbonne_oct_2020.osm @@ -0,0 +1,16376 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +