├── canvas-game
├── mazebot.png
├── mazebot-screenshot.png
├── js
│ ├── display
│ │ ├── status-bar.js
│ │ ├── styles.js
│ │ ├── avatar-path-renderer.js
│ │ ├── canvas-utils.js
│ │ ├── game-renderer.js
│ │ ├── overlay-renderer.js
│ │ └── map-renderer.js
│ ├── utils
│ │ ├── api-client.js
│ │ ├── pathfinder.js
│ │ ├── coordinates.js
│ │ └── polyfills.js
│ ├── index.js
│ └── game
│ │ ├── maze-input.js
│ │ └── maze-game.js
├── index.css
├── README.md
└── index.html
├── entries
└── chapmanj
│ ├── cert.txt
│ ├── README.md
│ └── mazebot-chapmanj-secret.ipynb
├── mazebot.rb
├── README.md
└── API.md
/canvas-game/mazebot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noops-challenge/mazebot/HEAD/canvas-game/mazebot.png
--------------------------------------------------------------------------------
/canvas-game/mazebot-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noops-challenge/mazebot/HEAD/canvas-game/mazebot-screenshot.png
--------------------------------------------------------------------------------
/entries/chapmanj/cert.txt:
--------------------------------------------------------------------------------
1 | {'result': 'finished', 'message': 'Congratulations chapmanj! You completed the challenge in 579.352 seconds. I am happy to present you with the attached certificate.', 'certificate': '/mazebot/race/certificate/UMmvQ9yLbiPgPy8-4ameLEETARoqlEA-PIXlKiGBNan8PGqZ1PVizccBhktdMQ2g'}
--------------------------------------------------------------------------------
/canvas-game/js/display/status-bar.js:
--------------------------------------------------------------------------------
1 | // bottom of the game -- status bar
2 | function StatusBar() {
3 |
4 | return {
5 | setMaze: setMaze,
6 | setPosition: setPosition,
7 | setMoveCount: setMoveCount
8 | }
9 |
10 | function setMaze(maze) {
11 | document.getElementById('current-maze').innerText = maze.name;
12 | setMoveCount(0);
13 | setPosition(maze.startingPosition);
14 | }
15 |
16 | function setMoveCount(c) {
17 | document.getElementById('move-count').innerText = c;
18 | }
19 |
20 | function setPosition(position) {
21 | document.getElementById('current-position').innerText = position[0] + ', ' + position[1];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/canvas-game/js/display/styles.js:
--------------------------------------------------------------------------------
1 | // fills used throughout the renderer
2 | // if only there was an api that generated random colors,
3 | // this would be a good place to use it
4 | var Styles = {
5 | mapBackground: '#222222',
6 | wallLayer1: "#111111",
7 | wallLayer2: "white",
8 | corner: "white",
9 | highlight: "#BBAA44",
10 | shadow: "#445566",
11 | avatarPath: "#FFFF00",
12 | tileOutline: [
13 | '#555555',
14 | '#333333',
15 | ],
16 | endcapFill: [
17 | 'rgb(36,154,53)',
18 | 'rgb(251, 12, 43)',
19 | 'rgb(56, 109, 188)',
20 | ],
21 | goalDirection: [
22 | '#13F6D2',
23 | '#43C610',
24 | '#54D632',
25 | '#23CF22'
26 | ]
27 | };
28 |
--------------------------------------------------------------------------------
/canvas-game/js/utils/api-client.js:
--------------------------------------------------------------------------------
1 | var API_ROOT = 'https://api.noopschallenge.com';
2 |
3 | function getJson(path) {
4 | return fetch(API_ROOT + path, {
5 | method: 'GET',
6 | headers: {
7 | 'Cache-Control': 'no-cache',
8 | 'Pragma': 'no-cache',
9 | 'Expires': 'Sat, 01 Jan 2000 00:00:00 GMT'
10 | }
11 | }).then(
12 | function(r) {
13 | if (!r.ok) throw new Error('Error fetching maze from' + path + '.');
14 | return r.json();
15 | }
16 | );
17 | }
18 |
19 | function postJson(path, body) {
20 | return fetch(API_ROOT + path, {
21 | headers: {
22 | 'content-type': 'application/json'
23 | },
24 | method: 'POST',
25 | body: JSON.stringify(body)
26 | }).then(
27 | function(r) { return r.json(); }
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/canvas-game/js/display/avatar-path-renderer.js:
--------------------------------------------------------------------------------
1 | // Renders the avatar's path through the maze
2 | function AvatarPathRenderer(
3 | avatarPathContext,
4 | coordinates
5 | ) {
6 |
7 | var positions = [];
8 |
9 | return {
10 | clearAvatarPath: clearAvatarPath,
11 | addToAvatarPath: addToAvatarPath,
12 | redraw: redraw
13 | };
14 |
15 | // redraw the whole path.
16 | // this happens when display size changes or viewport scrolls
17 | function redraw() {
18 | CanvasUtils.resetCanvas(avatarPathContext, "rgba(0,0,0,0)");
19 | for(var i = 0; i < positions.length - 1; i++) {
20 | renderSegment(positions[i], positions[i + 1]);
21 | }
22 | }
23 |
24 | function clearAvatarPath() {
25 | positions = [];
26 | CanvasUtils.resetCanvas(avatarPathContext, "rgba(0,0,0,0)");
27 | }
28 |
29 | // add a new segment. We don't redraw the whole path, just add to the avatar path canvas.
30 | function addToAvatarPath(from, to) {
31 | if (positions.length === 0) positions = [from];
32 | positions.push(to);
33 |
34 | renderSegment(from, to);
35 | }
36 |
37 | function renderSegment(from, to) {
38 | var f = coordinates.canvasPosition(from);
39 | var t = coordinates.canvasPosition(to);
40 | // only if we can see it!
41 | if (f.offscreen && t.offscreen) return;
42 |
43 | var dashBasis = Math.ceil(f.size / 20);
44 |
45 | CanvasUtils.drawPath(avatarPathContext, f.size / 8, Styles.avatarPath, [f.center, t.center], { dash: [dashBasis * 5, dashBasis * 5] });
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/canvas-game/js/display/canvas-utils.js:
--------------------------------------------------------------------------------
1 | var CanvasUtils = {
2 | resetCanvas: function (ctx, color) {
3 | ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
4 | ctx.fillStyle = color;
5 | ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
6 | },
7 | drawPath: function (ctx, width, style, coords, opts) {
8 | opts = opts || {};
9 |
10 | ctx.strokeStyle = style;
11 | ctx.lineWidth = Math.max(Math.round(width), 1);
12 | ctx.lineCap = 'round';
13 |
14 | ctx.setLineDash(opts.dash || []);
15 | ctx.beginPath();
16 | ctx.moveTo(Math.round(coords[0][0]), Math.round(coords[0][1]));
17 |
18 | for (var i = 1; i < coords.length; i++) {
19 | ctx.lineTo(Math.round(coords[i][0]), Math.round(coords[i][1]));
20 | }
21 | ctx.stroke();
22 | if (opts.fill) {
23 | ctx.fillStyle = opts.fill;
24 | ctx.fill();
25 | }
26 | },
27 | drawFilledCircle: function (ctx, center, radius, lineWidth, stroke, fill) {
28 | ctx.lineWidth = lineWidth;
29 | ctx.fillStyle = fill;
30 | ctx.strokeStyle = stroke;
31 | ctx.beginPath();
32 | ctx.arc(Math.round(center[0]), Math.round(center[1]), radius, 0, 2 * Math.PI, false);
33 | ctx.stroke();
34 | ctx.fill();
35 | },
36 | drawArc: function (ctx, center, radius, from, to, lineWidth, stroke) {
37 | ctx.lineWidth = lineWidth;
38 | ctx.strokeStyle = stroke;
39 | ctx.beginPath();
40 | ctx.arc(Math.round(center[0]), Math.round(center[1]), radius, from, to, false);
41 | ctx.stroke();
42 | }
43 | };
44 |
--------------------------------------------------------------------------------
/canvas-game/js/utils/pathfinder.js:
--------------------------------------------------------------------------------
1 | // Pathfinder takes a map and finds a path from one point to another, up to
2 | // the provided max length.
3 | // This lets us support navigating by clicking on map squares.
4 |
5 | function Pathfinder() {
6 | var map, maxLength = 1;
7 |
8 | return {
9 | setMap: setMap,
10 | setMaxLength: setMaxLength,
11 | findPath: findPath
12 | }
13 |
14 | function setMap(m) {
15 | map = m;
16 | }
17 |
18 | function setMaxLength(value) {
19 | maxLength = value;
20 | }
21 |
22 | function findPath(from, to) {
23 | return _findPath(from, to, [])
24 | }
25 |
26 | // recursively search all cardinal directions until we hit a wall or the target
27 | // This is not a very smart pathfinder
28 | function _findPath(c, to, soFar) {
29 | var x = c[0];
30 | var y = c[1];
31 | if (((map[y] || [])[x] || 'X') === 'X') return;
32 | if (distance(c, to) === 0) return soFar;
33 |
34 | if (soFar.length < maxLength) {
35 | var paths = [
36 | _findPath([x, y - 1], to, soFar.concat(['N'])),
37 | _findPath([x, y + 1], to, soFar.concat(['S'])),
38 | _findPath([x + 1, y], to, soFar.concat(['E'])),
39 | _findPath([x - 1, y], to, soFar.concat(['W']))
40 | ].filter(function(p) { return !!p; });
41 |
42 | if (paths.length) {
43 | paths.sort(shortest);
44 | return paths[0];
45 | }
46 | }
47 | }
48 |
49 | // comparator for finding shortest path among many
50 | function shortest(a, b) {
51 | return a.length - b.length;
52 | }
53 |
54 | // minimum number of moves from a to b
55 | // (x distance + y distance)
56 | function distance(a, b) {
57 | return Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/canvas-game/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Roboto', sans-serif;
3 | font-size: 18px;
4 | }
5 |
6 | h1, h2, h3, h4 {
7 | font-family: 'Roboto Medium', sans-serif;
8 | }
9 |
10 | p {
11 | margin: 20px auto;
12 | max-width: 700px;
13 | }
14 |
15 | .mazebot-image {
16 | max-width: 40vw;
17 | max-height: 40vh;
18 | margin: 20px;
19 | }
20 |
21 | button#start, button#next-maze {
22 | font-family: 'Roboto', sans-serif;
23 | font-size: 30px;
24 | border-radius: 7px;
25 | margin-top: 20px;
26 | padding: 10px 20px;
27 | }
28 |
29 | #game {
30 | position: fixed;
31 | top: 0;
32 | right: 0;
33 | bottom: 0;
34 | left: 0;
35 | display: none;
36 | }
37 |
38 | #get-started, #results, #error {
39 | display: none;
40 | position: absolute;
41 | top: 0;
42 | left: 0;
43 | bottom: 0;
44 | right: 0;
45 | text-align: center;
46 | }
47 |
48 | .phase-starting #get-started{
49 | display: block;
50 | }
51 |
52 | .phase-results #results {
53 | display: block;
54 | }
55 |
56 | .phase-playing #game {
57 | display: block;
58 | }
59 |
60 | .phase-error #error {
61 | display: block;
62 | }
63 | #board, #overlay, #avatar-path {
64 | position: absolute;
65 | top: 0;
66 | left: 0;
67 | }
68 |
69 | #board {
70 | z-index: 1;
71 | }
72 | #avatar-path {
73 | z-index: 1;
74 | }
75 | #overlay {
76 | z-index: 3;
77 | cursor: crosshair;
78 | }
79 |
80 | #status-bar {
81 | padding: 5px 10px 10px;
82 | z-index: 4;
83 | display: fixed;
84 | height: 30px;
85 | bottom: 0;
86 | width: 100%;
87 |
88 | background:white;
89 | display: flex;
90 | border-top: 2px solid #222222;
91 | }
92 |
93 | #status-bar div {
94 | flex: 1 1 33%;
95 | }
96 |
97 | .status-label {
98 | text-align: center;
99 | font-size: 8px;
100 | color: #666666;
101 | }
102 |
103 | .status-value {
104 | text-align: center;
105 | }
106 |
--------------------------------------------------------------------------------
/mazebot.rb:
--------------------------------------------------------------------------------
1 | #
2 | # Ruby automated mazebot racer example
3 | #
4 | # Can you write a program that finishes the race?
5 | #
6 | require "net/http"
7 | require "json"
8 |
9 | def main
10 | # get started — replace with your login
11 | start = post_json('/mazebot/race/start', { :login => 'mazebot' })
12 |
13 | maze_path = start['nextMaze']
14 | # get the first maze
15 | next_maze = get_json(maze_path)
16 | # Answer each question, as long as we are correct
17 | loop do
18 |
19 | # print out the map
20 | puts
21 | puts "Here's the map:"
22 | puts
23 | next_maze['map'].each { |row| puts(row.join('')) }
24 | puts
25 |
26 | puts "What are the directions from point A to point B?"
27 |
28 | # your code to figure out the answer could also go here
29 | directions = gets.delete("\n")
30 |
31 | # send to mazebot
32 | solution_result = send_solution(maze_path, directions)
33 | if solution_result['result'] == 'success'
34 | maze_path = solution_result['nextMaze']
35 | next_maze = get_json(maze_path)
36 | end
37 | end
38 | end
39 |
40 | def send_solution(path, directions)
41 | post_json(path, { :directions => directions })
42 | end
43 |
44 | # get data from the api and parse it into a ruby hash
45 | def get_json(path)
46 | puts "*** GET #{path}"
47 |
48 | response = Net::HTTP.get_response(build_uri(path))
49 | result = JSON.parse(response.body)
50 | puts "HTTP #{response.code}"
51 |
52 | #puts JSON.pretty_generate(result)
53 | result
54 | end
55 |
56 | # post an answer to the noops api
57 | def post_json(path, body)
58 | uri = build_uri(path)
59 | puts "*** POST #{path}"
60 | puts JSON.pretty_generate(body)
61 |
62 | post_request = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
63 | post_request.body = JSON.generate(body)
64 |
65 | response = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => true) do |http|
66 | http.request(post_request)
67 | end
68 |
69 | puts "HTTP #{response.code}"
70 | result = JSON.parse(response.body)
71 | puts result[:result]
72 | result
73 | end
74 |
75 | def build_uri(path)
76 | URI.parse("https://api.noopschallenge.com" + path)
77 | end
78 |
79 | main()
80 |
--------------------------------------------------------------------------------
/canvas-game/js/display/game-renderer.js:
--------------------------------------------------------------------------------
1 | // Top-level renderer
2 | // Sets up the renderers for each layer
3 | var Renderer = function (
4 | coordinates,
5 | mapCanvas,
6 | avatarPathCanvas,
7 | overlayCanvas
8 | ) {
9 |
10 | // There are 3 layers, listed here from bottom to top.
11 | // Each layer is sized to the entire viewport size
12 |
13 | // The map: floor and walls
14 | // Only updated when a maze starts or the viewport changes
15 | // (scroll, or zoom)
16 | var mapContext = mapCanvas.getContext('2d', {alpha: false});
17 |
18 | var mapRenderer = MapRenderer(
19 | mapContext,
20 | coordinates
21 | );
22 |
23 | // Avatar path: transparent and only updated when the path changes
24 | // or the viewport changes
25 | var avatarPathContext = avatarPathCanvas.getContext('2d');
26 |
27 | var avatarPathRenderer = AvatarPathRenderer(
28 | avatarPathContext,
29 | coordinates
30 | );
31 |
32 |
33 | // Overlay with the avatar and goal
34 | // This is continuously animated content, and is updated every frame
35 | var overlayContext = overlayCanvas.getContext('2d');
36 | var overlayRenderer = OverlayRenderer(
37 | overlayContext,
38 | coordinates
39 | );
40 |
41 | // New map: configure all the renderers
42 | function setMap(map) {
43 | CanvasUtils.resetCanvas(mapContext);
44 | mapRenderer.setMap(map);
45 | avatarPathRenderer.clearAvatarPath();
46 | displaySizeChanged();
47 | }
48 |
49 | // Resize all the canvases to the new size and tell the renderers to do their thing
50 | function displaySizeChanged() {
51 | var displaySize = coordinates.displaySize();
52 |
53 | mapCanvas.width = displaySize.width;
54 | mapCanvas.height = displaySize.height;
55 |
56 | avatarPathCanvas.width = displaySize.width;
57 | avatarPathCanvas.height = displaySize.height;
58 |
59 | overlayCanvas.width = displaySize.width;
60 | overlayCanvas.height = displaySize.height;
61 |
62 | // overlay is updated continuously so doesn't need to be here
63 | mapRenderer.render();
64 | avatarPathRenderer.redraw();
65 | }
66 |
67 | return {
68 | // called when a maze starts
69 | setMap: setMap,
70 | // called in render loop
71 | updateOverlay: overlayRenderer.updateOverlay,
72 | displaySizeChanged: displaySizeChanged,
73 | // player moved
74 | addToAvatarPath: avatarPathRenderer.addToAvatarPath
75 | };
76 | }
77 |
--------------------------------------------------------------------------------
/canvas-game/README.md:
--------------------------------------------------------------------------------
1 | # 🎮 Mazebot game starter
2 |
3 | Here's an example HTML game that uses the Mazebot API. that renders a random maze and allows the player to navigate around it to find the solution.
4 |
5 |
6 | Check out the [live demo](https://noops-challenge.github.io/mazebot/canvas-game) or clone this repository to your computer and open `index.html` in your browser to play locally.
7 |
8 | This game is written without libraries and no build tools -- just JavaScript, HTML, and CSS. If you want to change it, just edit the files and refresh your browser.
9 |
10 | It uses the [HTML canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) to render the game.
11 |
12 | #### Controls:
13 |
14 | Use the arrow keys, mouse or touch to navigate the maze.
15 | Plus key (+) or double-click/tap to zoom in/out.
16 |
17 | # ✨ A few ideas
18 |
19 | - **Change the difficulty:** Right now, the size of the maze can be controlled by setting the `minWidth` and `maxWidth` query parameters, which are [passed along to the Mazebot API](./js/index.js#L37). You could make the difficulty increase as the game progresses, or let the player select their difficulty when the game starts.
20 |
21 | - **Change the graphics:** Check out the [display rendering code](./js/display) to get some ideas. You could start by changing [the colors](./js/display/styles.js)... or maybe you'd like a different [avatar](./js/display/overlay-renderer.js) -- try making a vehicle that drives or a creature that walks! You could change the [map](./js/display/map-renderer.js): try replacing the graphics with a [tileset](). You could even completely replace the renderer with your own 3D or isometeric renderer!
22 |
23 | - **Add a solver:** Perhaps you'd like to add a solver to the game that can be [triggered somehow](./js/game/maze-input#L141) by the player.
24 |
25 | - **Add a soundtrack:** Add some sound effects using the WebAudio API, or background music while you play.
26 |
27 | - **Add enemies:** Make the maze harder by adding enemies that chase the player around the maze.
28 |
29 | - **Add different controls:** You could add support for vim keys to the [keyboard handler](./js/game/maze-input), or try to add [tilt controls](https://developer.mozilla.org/en-US/docs/Web/API/Detecting_device_orientation#Processing_motion_events) for players on mobile devices.
30 |
31 | - **Animate avatar movement:** The avatar moves instantly, and can move as fast as the player can move their mouse or finger. You could try to animate the movement in the [game loop](./js/game/maze-game.js#L61) to make the game smoother.
32 |
33 | Mazebot looks forward to seeing what you can do!
34 |
--------------------------------------------------------------------------------
/canvas-game/js/index.js:
--------------------------------------------------------------------------------
1 | //
2 | // Entrypoint for Mazebot game example
3 | //
4 | // - Sets up the game on window load
5 | // - Manages the "phase" ("starting", "playing", or "results") of the game.
6 | // - Connects data from the api to the game (fetches new mazes and sends solutions for checking)
7 | // - Listens for window resize and tells the game to resize itself.
8 | //
9 |
10 | var statusBarContainer;
11 | var game;
12 |
13 | window.onload = function() {
14 |
15 | // Set up the maze game
16 | statusBarContainer = document.getElementById('status-bar');
17 |
18 | // see game/maze-game.js
19 | game = MazeGame(
20 | document.getElementById('map'),
21 | document.getElementById('avatar-path'),
22 | document.getElementById('overlay'),
23 | statusBarContainer,
24 | window.innerWidth,
25 | window.innerHeight - statusBarContainer.clientHeight
26 | );
27 |
28 | // listen for resize
29 | window.onresize = handleResize;
30 |
31 | // hook up our two buttons that start a maze (on start and after a maze is completed)
32 | document.getElementById('start').addEventListener('click', startNewMaze);
33 | document.getElementById('next-maze').addEventListener('click', startNewMaze);
34 | }
35 |
36 | function startNewMaze() {
37 | //
38 | // one fun thing to do would be to use the minSize and maxSize query parameters
39 | // here to make the game get harder over time.
40 | //
41 | // in this case, we just pass along the search query
42 | getJson('/mazebot/random' + window.location.search).then(
43 | function(r) {
44 | setPhase('playing');
45 | game.startMaze(r, onSolution);
46 | handleResize();
47 | },
48 | function(err) {
49 | document.getElementById('error-message').innerText = err.message;
50 | setPhase('error');
51 | }
52 | );
53 | }
54 |
55 | // Setting the phase shows/hides the dom elements that are
56 | // relevant
57 | function setPhase(phase) {
58 | var el = document.getElementById('maze-browser');
59 | el.classList.remove('phase-starting');
60 | el.classList.remove('phase-playing');
61 | el.classList.remove('phase-results');
62 | el.classList.remove('phase-error');
63 | el.classList.add('phase-' + phase);
64 | }
65 |
66 | function handleResize() {
67 | game.setDisplaySize(window.innerWidth, window.innerHeight - statusBarContainer.clientHeight);
68 | }
69 |
70 | // when a maze is solved, show the results div and await a click to continue
71 | function onSolution(result) {
72 | document.getElementById('results-message').innerText = result.message;
73 | setPhase('results');
74 | document.getElementById('next-maze').focus();
75 | }
76 |
--------------------------------------------------------------------------------
/entries/chapmanj/README.md:
--------------------------------------------------------------------------------
1 |
2 | # 1. Maze Traveler Code
3 |
4 | ## 1.1 NOOP Challenge GET and POST functions
5 | These were based on the examples given in the API README
6 |
7 | ## 1.2 Walker Class
8 | A representation of a being in a maze with properties:
9 |
10 | ### 1.2.1 Parameters
11 | - `x`, `y `: location
12 | - `directions`: list of moves it has made (ex. `'NEWS'`)
13 | - `locations`: list of visited locations related to .directions list
14 | - `visited`: list of all visited nodes regardless of .directions or .locations
15 |
16 | ### 1.2.1 Methods
17 | A Walker has the methods: `N`, `E`, `W`, and `S` used to move the walker, changing its coordinates and storing the direction and location to their appropriate lists:
18 |
19 | A walker has a method to set its location (used when backtracking).
20 | - `set_at`: set the Walkers `x`,`y` to a node, `locations[index]`'s `x`,`y`
21 | - `clone`: generates unique instance of the walker (I used it to evaluate neighboring solutions)
22 |
23 | ## 1.3 Dungeon_master Class
24 | The solver that manipulates a Walker to determine a solution to the passed in maze/map
25 |
26 | ### 1.3.1 Parameters
27 | - `maze` The Dungeon_master stores a passed in instance of a maze
28 |
29 | ### 1.3.2 Methods
30 | - `solve` The main logic of the solution process.
31 |
32 | #### ✨Suggest Methods
33 |
34 | - `LOOK_AROUND`
35 | Determine the state of each possible move (NEWS) from current location
36 |
37 | - `MOVE_FORWARD`
38 | Use the information gained in step one, along with the Walker's `locations` and `visited` lists to determine the next move
39 |
40 | - `BACKTRACK`
41 | If the Walker has reached a terminal non-goal location, determine what point in `locations` it should return to and continue searching in a differnt direction. Ensure that the backtrack process removes unwanted directions and locations leading to terminal location.
42 |
43 | - `FEEDBACK`
44 | Provide information about the Walkers progress as it searches.
45 |
46 | ## 1.4 Helper Functions
47 | Because long lines of display are sometimes hard to manage the following functions attempt to compactly display (smaller) mazes.
48 |
49 | - `draw_maze`: displays the maze, optionally, with the walkers tour indicated by '='
50 |
51 | #### ✨Suggest Functions
52 | I would also suggest creating functions to concisely display data generated during the solution process. (ex. string of neighboring moves, string of Walker.locations)
53 |
54 | # 2. Solve a maze
55 | 1. Get a new maze from the Mazebot or navigate to a maze's url and save the JSON to work on a single instance (like the one provided).
56 |
57 | 2. Define a Walker
58 |
59 | 3. Define a Dungeon_master with a maze
60 |
61 | 4. Solve the maze with the Dungeon_master and Walker
62 |
63 | # 3. RaceMode
64 | 1. Initiate the race
65 | 2. While `nextMaze` is returned, solve that maze
66 | 2. Win!
67 |
68 |
69 | ```python
70 |
71 | ```
72 |
--------------------------------------------------------------------------------
/canvas-game/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Mazebot
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
38 |
39 |
40 |
41 |
42 |
43 |
Welcome to Mazebot
44 |

45 |
Use your arrow keys, mouse, or touch to navigate the maze
46 |
Press + or double click to zoom in or out
47 |
48 |
49 |
50 |
51 |
52 |
53 |
RESULTS
54 |

55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
OOPS!
63 |

64 |
Something went wrong there.
65 |
66 |
Please refresh the page.
67 |
68 |
69 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/canvas-game/js/game/maze-input.js:
--------------------------------------------------------------------------------
1 | // Input handling for ...
2 | //
3 | // - mouse input
4 | // - keyboard inpot
5 | // - touch input
6 | //
7 | // ... that converts user actions to *maze* coordinates.
8 | //
9 | // If you want to add support for vim or emacs keys, this is the place to do it.
10 | //
11 | function MazeInput(coordinates, overlay) {
12 | function noop() { } // ;^)
13 |
14 | var mouseOrTouchActive = false;
15 | var mousePos = [];
16 | var onPosition = noop;
17 | var onDirection = noop;
18 | var onZoom = noop;
19 | var lastTap = 0;
20 | var firstTouch;
21 |
22 | var last10 = [];
23 |
24 | document.addEventListener('gesturestart', function (e) {
25 | e.preventDefault();
26 | });
27 |
28 | //window.addEventListener('keydown', handleKeydown);
29 | window.addEventListener('keydown', handleKeydown);
30 |
31 | overlay.addEventListener('dblclick', handleMapDoubleClick);
32 |
33 | overlay.addEventListener('mousedown', handleMousedown);
34 | overlay.addEventListener('mouseup', handleMouseup);
35 | overlay.addEventListener('mousemove', handleMousemove);
36 |
37 | overlay.addEventListener('touchstart', handleTouchstart);
38 | overlay.addEventListener('touchend', handleTouchend);
39 | overlay.addEventListener('touchmove', handleTouchmove);
40 |
41 | return {
42 | setHandlers: setHandlers
43 | };
44 |
45 | // these are the handlers that are invoked when a position is clicked
46 | // or a direction key is pressed.
47 | // They are all set at once for convenience.
48 | //
49 | // Note that these handlers are always invoked with maze coordinates.
50 | function setHandlers(onPosition_, onDirection_, onZoom_) {
51 | onPosition = onPosition_ || noop;
52 | onDirection = onDirection_ || noop;
53 | onZoom = onZoom_ || noop;
54 | mouseOrTouchActive = false;
55 | }
56 |
57 | //
58 | // Mouse handling: double-click for zoom, mouse hold and move for moving.
59 | //
60 | function handleMapDoubleClick(e) {
61 | var pos = coordinates.mapPosition([e.clientX, e.clientY]);
62 | onZoom(pos);
63 | }
64 |
65 | function handleMousedown(e) {
66 | notifyMove(e);
67 | mouseOrTouchActive = true;
68 | }
69 |
70 | function handleMouseup(e) {
71 | mouseOrTouchActive = false;
72 | mouseCell = [];
73 | }
74 |
75 | function handleMousemove(e) {
76 | if (mouseOrTouchActive) {
77 | notifyMove(e);
78 | }
79 | }
80 |
81 | //
82 | // This touch handling is pretty basic and a bit rough.
83 | //
84 |
85 | // on start, set
86 | function handleTouchstart(e) {
87 | firstTouch = e.touches[0];
88 | mouseOrTouchActive = true;
89 | }
90 |
91 | function handleTouchmove(e) {
92 | var touch = e.changedTouches[0];
93 | notifyMove(touch);
94 | e.preventDefault();
95 | }
96 |
97 | function handleTouchend(e) {
98 | mouseOrTouchActive = false;
99 | if (lastTap > Date.now() - 500) {
100 | notifyZoom(firstTouch);
101 | e.preventDefault();
102 | }
103 | lastTap = Date.now();
104 | }
105 |
106 | // convert from client coordinates to grid coordinates and notify
107 | // the listener of a move attempt (click on grid square)
108 | function notifyMove(e) {
109 | var pos = coordinates.mapPosition([e.clientX, e.clientY]);
110 | if (pos[0] !== mousePos[0] || pos[1] !== mousePos[1]) {
111 | onPosition(pos);
112 | }
113 | mousePos = pos;
114 | }
115 |
116 | // convert an event with client coordinates into a zoom
117 | // event to the corresponding grid position
118 | function notifyZoom(e) {
119 | var pos = coordinates.mapPosition([e.clientX, e.clientY]);
120 | onZoom(pos);
121 | }
122 |
123 | // Handle arrow keys.
124 |
125 | function handleKeydown(e) {
126 | const key = e.key.replace(/^Arrow/, '');
127 |
128 | var directions = {
129 | Right: 'E',
130 | Left: 'W',
131 | Up: 'N',
132 | Down: 'S',
133 | }
134 |
135 | if (checkKonami(key)) console.log('KONAMI CODE ACTIVATED');
136 | var direction = directions[key];
137 | if (direction) onDirection(direction);
138 | if (key === '=' || key === '+') onZoom();
139 | }
140 |
141 | // ohai what's this?
142 | // this would be a good place to invoke a maze solver if you had one.
143 | function checkKonami(code) {
144 | var k = ['Up', 'Up', 'Down', 'Down', 'Left', 'Right', 'Left', 'Right', 'b', 'a'];
145 | last10.push(code);
146 | if (last10.length > 10) last10.shift();
147 | if (last10.length !== 10) return false;
148 | for (var i = 0; i < 10; i++) {
149 | if (last10[i] !== k[i]) return false;
150 | }
151 | return true;
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # 👋 Meet Mazebot
4 | For many years, Mazebot spent most of its vacation days and countless lunch breaks mapping the many forgotten cavernous subterranean floors beneath the Noops factory.
5 |
6 | Spending all that time getting lost and finding its way took its toll on Mazebot.
7 |
8 | These days, Mazebot is excited about only one thing: Challenging you to solve mazes.
9 |
10 | Are you ready to solve them?
11 |
12 | Start with the smaller mazes. Once you get the basics down, enter the Mazebot 500, a race to solve a series of mazes in a row.
13 |
14 | IF you can complete them all, you will get a certificate of your achievement.
15 |
16 | # 🤖 API
17 |
18 | Each maze in Mazebot's collection has a map that is a an array of rows.
19 |
20 | Each row is an array of single-length strings representing the contents of each location in the maze.
21 |
22 | The possible values are:
23 |
24 | - `" "` empty - a passable square
25 | - `"X"` - wall - not passable
26 | - `"A"` - starting position - this is where you start in the maze
27 | - `"B"` - goal - this is where you need to navigate to to escape the maze
28 |
29 | The rows are in order from north to south, and the entries in each column are in order from west to east.
30 |
31 | In these mazes, you may travel in any of the four cardinal directions ("N", "E", "S", "W").
32 |
33 | ## ✳️ How to play
34 |
35 | Mazebot offers two ways to play: random mode, and the great maze race.
36 |
37 | See the [API documentation](./API.md) for more information.
38 |
39 | ## 🎲 Random mode
40 |
41 | Mazebot will give you a random selection from its maze collection and see how fast you can solve it.
42 |
43 | You can optionally limit the sizes of maze you would like with the `minSize` and `maxSize` parameters.
44 |
45 | ### Get a random maze
46 |
47 | `GET /mazebot/random`
48 |
49 | ### Get a maze that is at least 20 squares wide.
50 |
51 | `GET /mazebot/random?minSize=20`
52 |
53 | ### Get a maze that is between 30 and 60 squares wide.
54 |
55 | `GET /mazebot/random?minSize=30&maxSize=60`
56 |
57 | ## 🏎️ Race mode
58 |
59 | In race mode, mazebot will give you a series of mazes and challenge you to solve them all. At the end, if you are successful, Mazebot will award you a certificate that you can use to prove your maze mettle.
60 |
61 | ### Get information about the race
62 |
63 | `GET /mazebot/race`
64 |
65 | ### Start the race
66 |
67 | `POST /mazebot/race { "login": "yourgithubnamehere" }`
68 |
69 | # Starter Kits
70 |
71 | ## Ruby command line client
72 |
73 | You can start building a command-line solver by starting with the [the included ruby script](./mazebot.rb).
74 |
75 | The script demonstrates how to access the Mazebot API and work through the Mazebot race.
76 |
77 | Can you build a program that can complete the Mazebot 500 on its own?
78 |
79 | ## HTML5 Canvas Maze Game
80 |
81 | Mazebot has also included an [HTML Game](./canvas-game) that will let you play through random mazes from the Mazebot API.
82 |
83 | Check out the [source code](./canvas-game) and show us your forks.
84 |
85 | [Play it now!](https://noopschallenge.github.io/canvas-game/index.html)
86 |
87 | 
88 |
89 | Look at [the README](./canvas-game/README.md) for more information and ideas for extending the game.
90 |
91 | # ✨ A few ideas
92 |
93 | - **Create an automated solver**: Humans can be pretty good at solving mazes, but they'll never be as fast as a well-tuned computer. You could start from the [the included ruby script](./mazebot.rb) or start from scratch in another language. If you create a solver in another language, please share it with the Noops!
94 |
95 | - **Extend the game:** Check out [the canvas game README](./canvas-game/README.md) for some ideas on how to get started.
96 |
97 | - **Create your own maze game:** Try using a tool like [Phaser](http://phaser.io/) to create your own game using the Mazebot API.
98 |
99 | - **Create your own maze API:** Try making your own API that serves random mazes and connect the canvas maze game to it.
100 |
101 | - **Create a colorful terminal client** Do you have a fondness for ASCII graphics? Create a fun client
102 |
103 | - **Generate art with the mazes**: Solving these mazes isn't the only thing you can do with them. Maybe you'd rather use these maze patterns to generate art or sound.
104 |
105 | - **Remix:** Try mixing in one of the [other Noops APIs](http://noopschallenge.com/challenges) to make something amazing.
106 |
107 | Mazebot can't wait to see what you make!
108 |
109 | More about Mazebot here: https://noopschallenge.com/challenges/mazebot
110 |
--------------------------------------------------------------------------------
/canvas-game/js/display/overlay-renderer.js:
--------------------------------------------------------------------------------
1 | // Render the overlay:
2 | // - the avatar
3 | // - the goal
4 | // this is continuously animating and is called many times per second
5 | function OverlayRenderer(
6 | overlayContext,
7 | coordinates
8 | ) {
9 | return {
10 | updateOverlay: function (avatarPosition, avatarDirection, endingPosition, frame) {
11 | CanvasUtils.resetCanvas(overlayContext, "rgba(0,0,0,0)");
12 |
13 | // goal first, so the avatar "floats" above if adjacent
14 | drawGoal(avatarPosition, endingPosition, frame);
15 | drawAvatar(avatarPosition, avatarDirection, frame);
16 | }
17 | };
18 |
19 | function drawGoal(currentPosition, endingPosition, time) {
20 | var position = coordinates.canvasPosition(endingPosition);
21 |
22 | if (position.offscreen) {
23 | // goal is offscreen - render a directional pointer to the goal
24 | var avatarScreenPosition = coordinates.canvasPosition(currentPosition);
25 |
26 | // figure out the direction
27 | var direction = angle(currentPosition, endingPosition);
28 | var distance = Math.abs(currentPosition[0] - endingPosition[0]) + Math.abs(currentPosition[1] - endingPosition[1]);
29 |
30 | // make it farther away from the avatar the farther from goal
31 | var distanceModifier = (Math.min(40, distance - 20) / 100);
32 | var radius = position.size * (0.85 + distanceModifier);
33 |
34 | // could vary this too...
35 | var lineWidth = 3;
36 |
37 | // cycle the colors based on current time
38 | var frame = Math.floor((time / 150) % Styles.goalDirection.length);
39 |
40 | // draw the arcs
41 | for (var i = 0; i < Styles.goalDirection.length; i++) {
42 | var magnitude = 0.08 * (i + 0.5);
43 | var style = Styles.goalDirection[(frame + i) % Styles.goalDirection.length]
44 | CanvasUtils.drawArc(overlayContext, avatarScreenPosition.center, radius * (1 - (i * 0.05)), direction - magnitude, direction + magnitude, lineWidth, style)
45 | }
46 | }
47 | else {
48 | // hrender a target
49 |
50 | // how big?
51 | if (position.size < 30) factor = factor * 1.2;
52 | if (position.size < 15) factor = factor * 1.2;
53 |
54 | // pulse the size over time
55 | var factor = Math.abs(time % 2000 - 1000) / 4000;
56 | var scale = 0.4 + factor;
57 | var basis = scale * position.size;
58 |
59 | CanvasUtils.drawFilledCircle(overlayContext, position.center, basis * 1.2, position.size / 10, "black", "yellow");
60 | if (coordinates.cellSize() > 12) {
61 | CanvasUtils.drawFilledCircle(overlayContext, position.center, basis, position.size / 9, "black", "yellow");
62 | }
63 | CanvasUtils.drawFilledCircle(overlayContext, position.center, basis * 0.8, position.size / 8, "red", "white");
64 | if (coordinates.cellSize() > 12) {
65 | CanvasUtils.drawFilledCircle(overlayContext, position.center, basis * 0.6, position.size / 8, "red", "yellow");
66 | }
67 | CanvasUtils.drawFilledCircle(overlayContext, position.center, basis * 0.4, position.size / 8, "red", "white");
68 | CanvasUtils.drawFilledCircle(overlayContext, position.center, basis * 0.2, position.size / 8, "black", "red");
69 | }
70 | }
71 |
72 | function angle(from, to) {
73 | var dy = to[1] - from[1];
74 | var dx = to[0] - from[0];
75 | return Math.atan2(dy, dx);
76 | }
77 |
78 | // Draw the arrow that represensts the avatar
79 | function drawAvatar(avatarPosition, avatarDirection, time) {
80 | var cell = coordinates.canvasPosition(avatarPosition);
81 |
82 | var scale = 0.8 - (0.0003 * Math.abs(time % 1000 - 500));
83 |
84 | // size for the display, kinda
85 | if (cell.size < 20) scale += 0.4;
86 | if (cell.size < 10) scale += 0.4;
87 |
88 | var c = cell.scale(scale);
89 | var width = Math.floor(cell.size / 15);
90 |
91 | // point the arrow
92 | if (avatarDirection === 'W') {
93 | drawArrow(overlayContext, c.w, c.ne, c.se, width);
94 | }
95 | else if (avatarDirection === 'E') {
96 | drawArrow(overlayContext, c.e, c.nw, c.sw, width);
97 | }
98 | else if (avatarDirection === 'S') {
99 | drawArrow(overlayContext, c.s, c.ne, c.nw, width);
100 | }
101 | else {
102 | drawArrow(overlayContext, c.n, c.se, c.sw, width);
103 | }
104 | }
105 |
106 |
107 | function drawArrow(overlayContext, tip, c1, c2, width) {
108 | //shadow
109 | CanvasUtils.drawPath(overlayContext, 2, 'black', shadowOffset(width, [c1, c2, tip, c1]), { fill: 'black' });
110 |
111 | // arrow
112 | CanvasUtils.drawPath(overlayContext, width, '#00E8F6', [c1, c2, tip, c1], { fill: 'yellow' });
113 | }
114 |
115 | function shadowOffset(width, points) {
116 | return points.map(function(p) { return [p[0] + width * 1.5, p[1] + width * 1.5] });
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/canvas-game/js/game/maze-game.js:
--------------------------------------------------------------------------------
1 | //
2 | // The top-level game manager
3 | //
4 | function MazeGame(
5 | mapCanvas,
6 | avatarPathCanvas,
7 | overlayCanvas,
8 | statusBarContainer,
9 | width,
10 | height
11 | ) {
12 |
13 | var currentMaze = null;
14 | var onSolution;
15 | var currentPosition = null;
16 | var moves = [];
17 |
18 | // Maps between display positions and map positions
19 | var coordinates = Coordinates();
20 | coordinates.setDisplaySize(width, height);
21 |
22 | // When user taps/drags in a position,
23 | // Find a path to the position so we can move them there
24 | // This is a really basic depth-first pathfinder
25 | var pathfinder = Pathfinder();
26 |
27 | // Handles user input on the maze and tells us about it:
28 | // click to move, keyboard movement, toggling zoom, etc.
29 | var mazeInput = MazeInput(coordinates, overlay);
30 |
31 | // Renders the maze
32 | var renderer = Renderer(
33 | coordinates,
34 | mapCanvas,
35 | avatarPathCanvas,
36 | overlayCanvas
37 | );
38 |
39 | // The status bar at the bottom of the screen
40 | var statusBar = StatusBar(statusBarContainer, coordinates);
41 |
42 | return {
43 | // start a new maze
44 | startMaze: startMaze,
45 |
46 | // when the screen size changes, re-render and configure the coordinate system
47 | setDisplaySize: setDisplaySize
48 | };
49 |
50 | function startMaze(maze, onSolution_) {
51 | moves = [];
52 | setMaze(maze);
53 | onSolution = onSolution_;
54 | renderFrame();
55 | }
56 |
57 | // the render loop.
58 | function renderFrame() {
59 | if (currentMaze) {
60 | // time is used for periodic animations
61 | var time = Date.now();
62 |
63 | // There are three stacked canvases:
64 | // the map, the avatar path, and the overlay with the avatar and goal.
65 | // Only the overlay is animated, so it is updated here
66 | renderer.updateOverlay(currentPosition, moves[moves.length - 1], currentMaze.endingPosition, time);
67 | }
68 |
69 | // do it again
70 | requestAnimationFrame(renderFrame);
71 | }
72 |
73 | // Configure the gmae with a new maze
74 | function setMaze(maze) {
75 | currentMaze = maze;
76 | currentPosition = maze.startingPosition;
77 |
78 | // pathfinder needs the map to find paths
79 | pathfinder.setMap(maze.map);
80 | pathfinder.setMaxLength(Math.min(10, maze.map.length));
81 |
82 | // set up the coordinate system that everything else relies upon
83 | coordinates.setMapSize(maze.map[0].length, maze.map.length);
84 | coordinates.zoom(maze.startingPosition)
85 |
86 | // set the map in the renderer, which will kick off map rendering etc.
87 | renderer.setMap(maze.map);
88 | statusBar.setMaze(maze);
89 |
90 | // turn input on. We ignore input when there is no game active.
91 | mazeInput.setHandlers(handleInputCell, handleInputDirection, handleInputZoom);
92 |
93 | }
94 |
95 | // user requested zoom, optionally with a position
96 | // (double-click/tap on a square as opposed to keyboard plus)
97 | function handleInputZoom(position) {
98 | if (coordinates.zoomed()) {
99 | coordinates.unzoom();
100 | }
101 | else {
102 | coordinates.zoom(position || currentPosition);
103 | }
104 |
105 | renderer.displaySizeChanged();
106 | }
107 |
108 | // User click, tapped, or dragged to a cell.
109 | // if it is reachable with the current pathfinder,
110 | // we move them there
111 | function handleInputCell(pos) {
112 | var path = pathfinder.findPath(currentPosition, pos);
113 | if (path) {
114 | path.forEach(handleInputDirection);
115 | }
116 | }
117 |
118 | // user hit an arrow key
119 | // if the neighboring square is open, move there
120 | function handleInputDirection(direction) {
121 | var nextPosition = applyDirectionToCurrentPosition(direction);
122 | if (isValidPosition(nextPosition)) {
123 | // save the move
124 | moves.push(direction);
125 |
126 | // update all the display bits
127 | statusBar.setMoveCount(moves.length);
128 | statusBar.setPosition(nextPosition);
129 | renderer.addToAvatarPath(currentPosition, nextPosition);
130 | currentPosition = nextPosition;
131 |
132 | // tell the coordinate system about the move
133 | // if it returns true that means it movesd the viewport
134 | // in which case we need to tell the renderer it changed
135 | if (coordinates.scrollIfNeeded(currentPosition, direction)) {
136 | renderer.displaySizeChanged();
137 | }
138 |
139 | // See if we have readched the goal
140 | checkForSolution();
141 | }
142 | }
143 |
144 | // configure display; see coordinates.js for the meat of what happnens here.
145 | function setDisplaySize(width, height) {
146 | coordinates.setDisplaySize(width, height);
147 | renderer.displaySizeChanged();
148 | }
149 |
150 | // given a direction, find the coordinates in that direction
151 | function applyDirectionToCurrentPosition(d) {
152 | var offsets = {
153 | N: [0, -1],
154 | S: [0, 1],
155 | W: [-1, 0],
156 | E: [1, 0],
157 | }
158 |
159 | return [currentPosition[0] + offsets[d][0], currentPosition[1] + offsets[d][1]];
160 | }
161 |
162 | // Check to see if we are at the goal.
163 | // If so, submit our solution to the Mazebot api
164 | // and call back with our result so we can show it
165 | // (ending the current maze)
166 | function checkForSolution() {
167 | if (
168 | currentPosition[0] === currentMaze.endingPosition[0] &&
169 | currentPosition[1] === currentMaze.endingPosition[1]
170 | ) {
171 | mazeInput.setHandlers(null, null);
172 | postJson(currentMaze.mazePath, {
173 | directions: moves.join('')
174 | }).then(function (result) {
175 | onSolution(result);
176 | });
177 | }
178 | }
179 |
180 | // is this a valid position to move to?
181 | // is it either the start, the end, or an open position
182 | function isValidPosition(p) {
183 | var map = currentMaze.map;
184 |
185 | // out of bounds
186 | if (
187 | p[0] < 0 ||
188 | p[1] < 0 ||
189 | p[0] >= map[0].length ||
190 | p[1] >= map.length
191 | ) return false;
192 |
193 | // wall
194 | if (map[p[1]][p[0]] === 'X') return false;
195 |
196 | return true;
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/API.md:
--------------------------------------------------------------------------------
1 | ## Mazebot API
2 |
3 | The Mazebot API (https://api.noopschallenge.com/mazebot) has two different modes: *random* and *race*.
4 |
5 | ## Random mode
6 |
7 | Mazebot will serve you random mazes. You can also use the query parameter `minSize` and `maxSize` to control the size of maze that is served.
8 |
9 | The available sizes are: 10, 20, 30, 40, 60, 100, 120, 150, and 200.
10 |
11 | ### getting a random maze
12 |
13 | `GET https://api.noopschallenge.com/mazebot/random`
14 |
15 | `HTTP 200`
16 |
17 | ```
18 | {
19 | "name": "Maze #236 (10x10)",
20 | "mazePath": "/mazebot/mazes/ikTcNQMwKhux3bWjV3SSYKfyaVHcL0FXsvbwVGk5ns8",
21 | "startingPosition": [ 4, 3 ],
22 | "endingPosition": [ 3, 6 ],
23 | "message": "When you have figured out the solution, post it back to this url. See the exampleSolution for more information.",
24 | "exampleSolution": { "directions": "ENWNNENWNNS" },
25 | "map": [
26 | [ " ", " ", "X", " ", " ", " ", "X", " ", "X", "X" ],
27 | [ " ", "X", " ", " ", " ", " ", " ", " ", " ", " " ],
28 | [ " ", "X", " ", "X", "X", "X", "X", "X", "X", " " ],
29 | [ " ", "X", " ", " ", "A", " ", " ", " ", "X", " " ],
30 | [ " ", "X", "X", "X", "X", "X", "X", "X", " ", " " ],
31 | [ "X", " ", " ", " ", "X", " ", " ", " ", "X", " " ],
32 | [ " ", " ", "X", "B", "X", " ", "X", " ", "X", " " ],
33 | [ " ", " ", "X", " ", "X", " ", "X", " ", " ", " " ],
34 | [ "X", " ", "X", "X", "X", "X", "X", " ", "X", "X" ],
35 | [ "X", " ", " ", " ", " ", " ", " ", " ", "X", "X" ]
36 | ]
37 | }
38 | ```
39 |
40 |
41 | ### getting a maze with size constraints
42 |
43 | `GET https://api.noopschallenge.com/mazebot/random?minSize=10&maxSize=20`
44 |
45 | `HTTP 200`
46 |
47 | ```
48 | {
49 | "name": "Maze #142 (10x10)",
50 | "mazePath": "/mazebot/mazes/dTXurZOonsCbWC9_PDBWpiRAvBME3VBDIf9hcwwCdNc",
51 | "startingPosition": [ 9, 3 ],
52 | "endingPosition": [ 7, 0 ],
53 | "message": "When you have figured out the solution, post it back to this url. See the exampleSolution for more information.",
54 | "exampleSolution": { "directions": "ENWNNENWNNS" },
55 | "map": [
56 | [ "X", " ", " ", " ", " ", " ", "X", "B", " ", " " ],
57 | [ " ", " ", " ", " ", "X", " ", " ", " ", "X", " " ],
58 | [ " ", "X", "X", "X", " ", "X", "X", "X", " ", "X" ],
59 | [ " ", " ", " ", " ", "X", " ", " ", "X", " ", "A" ],
60 | [ " ", "X", "X", "X", " ", "X", " ", "X", " ", " " ],
61 | [ " ", " ", " ", "X", " ", "X", " ", "X", " ", " " ],
62 | [ " ", "X", " ", "X", " ", "X", " ", "X", " ", "X" ],
63 | [ " ", " ", " ", "X", " ", "X", " ", "X", " ", " " ],
64 | [ "X", " ", "X", "X", " ", " ", " ", " ", " ", " " ],
65 | [ "X", " ", " ", " ", " ", "X", " ", " ", " ", "X" ]
66 | ]
67 | }
68 | ```
69 |
70 |
71 | ### solving a random maze
72 |
73 | `POST https://api.noopschallenge.com/mazebot/mazes/RPSpGy5aqL5p30LpQwqiO9O2yQipiLQT3WGqPSHtyVg`
74 |
75 |
76 | ```
77 | {
78 | "directions": "ENNNN...."
79 | }
80 | ```
81 |
82 | `HTTP 200`
83 |
84 | ```
85 | {
86 | "result": "success",
87 | "message": "You solved it in 0.029 seconds with 56 steps, the shortest possible solution.",
88 | "shortestSolutionLength": 56,
89 | "yourSolutionLength": 56,
90 | "elapsed": 29
91 | }
92 | ```
93 |
94 |
95 | ### incorrect solution
96 |
97 | `POST https://api.noopschallenge.com/mazebot/mazes/17pSAsql1EEaCvEe28UnAQ`
98 |
99 |
100 | ```
101 | {
102 | "directions": "ESS"
103 | }
104 | ```
105 |
106 | `HTTP 400`
107 |
108 | ```
109 | { "message": "Hit a wall at directions[1]", "result": "failed" }
110 | ```
111 |
112 |
113 | ### incorrect solution - did not finish
114 |
115 | `POST https://api.noopschallenge.com/mazebot/mazes/17pSAsql1EEaCvEe28UnAQ`
116 |
117 |
118 | ```
119 | {
120 | "directions": ""
121 | }
122 | ```
123 |
124 | `HTTP 400`
125 |
126 | ```
127 | { "message": "Did not make it to point B", "result": "failed" }
128 | ```
129 |
130 | ## Race mode
131 |
132 | Once you are getting good at solving mazes, join the Mazebot 500, a timed race to solve a series of mazes of increasing difficulty.
133 | If you can solve them all, Mazebot will grant you a certificate of your achievement.
134 |
135 | Be warned -- some of these mazes are really big! Maybe a robot would be better suited to this task than a human...
136 |
137 | ### get info about the maze race
138 |
139 | `GET https://api.noopschallenge.com/mazebot/race`
140 |
141 | `HTTP 200`
142 |
143 | ```
144 | {
145 | "message": "Welcome to the Mazebot 500.\n\nI will give you a series of mazes\nIf you can solve them all, I'll give you a certificate that proves your maze mettle.\n\nAre you ready to enter?\n\nWhen you are ready, send me your github login by posting json to /mazebot/race/start\n\nSee the attached exampleRequest.\n",
146 | "exampleRequest": { "login": "noops-challenge" }
147 | }
148 | ```
149 |
150 |
151 | ### Start the race with a POST
152 |
153 | `POST https://api.noopschallenge.com/mazebot/race/start`
154 |
155 |
156 | ```
157 | {
158 | "login": "yourgithubloginhere"
159 | }
160 | ```
161 |
162 | `HTTP 200`
163 |
164 | ```
165 | {
166 | "message": "Start your engines!",
167 | "nextMaze": "/mazebot/race/iEGpDT1I0qFzGU81yb49JY3Srj1daT70P6e-Zr6bpR0"
168 | }
169 | ```
170 |
171 |
172 | ### Get the first maze in the race from the provided URL
173 |
174 | `GET https://api.noopschallenge.com/mazebot/race/Fh5Kt7l9gMQr41GvWkmoCg`
175 |
176 | `HTTP 200`
177 |
178 | ```
179 | {
180 | "name": "Mazebot 500 Stage#1 (5x5)",
181 | "mazePath": "/mazebot/race/Fh5Kt7l9gMQr41GvWkmoCg",
182 | "map": [
183 | [ "A", " ", " ", " ", " " ],
184 | [ " ", "X", "X", "X", " " ],
185 | [ " ", " ", "X", " ", " " ],
186 | [ "X", " ", "X", "B", "X" ],
187 | [ "X", " ", " ", " ", "X" ]
188 | ],
189 | "message": "When you have figured out the solution, post it back to this url in JSON format. See the exampleSolution for more information.",
190 | "startingPosition": [ 0, 0 ],
191 | "endingPosition": [ 3, 3 ],
192 | "exampleSolution": { "directions": "ENWNNENWNNS" }
193 | }
194 | ```
195 |
196 |
197 | ### Solve the first maze in the race
198 |
199 | `POST https://api.noopschallenge.com/mazebot/race/Fh5Kt7l9gMQr41GvWkmoCg`
200 |
201 |
202 | ```
203 | {
204 | "directions": "SSE..."
205 | }
206 | ```
207 |
208 | `HTTP 200`
209 |
210 | ```
211 | {
212 | "result": "success",
213 | "elapsed": 0.52,
214 | "shortestSolutionLength": 8,
215 | "yourSolutionLength": 8,
216 | "nextMaze": "/mazebot/race/2os41shHeVDhaKTAw1m9-Q"
217 | }
218 | ``
219 |
--------------------------------------------------------------------------------
/canvas-game/js/utils/coordinates.js:
--------------------------------------------------------------------------------
1 | // Coordinate system that maps between display coordinates and maze coordinates
2 | function Coordinates() {
3 | var displayWidth = 1;
4 | var displayHeight = 1;
5 | var mapHeight = 1;
6 | var mapWidth = 1;
7 | var cellSize = 0;
8 |
9 | var displayOffsetX = 0;
10 | var displayOffsetY = 0;
11 |
12 | var cellOffsetX = 0;
13 | var cellOffsetY = 0;
14 | var displayCells = 1;
15 |
16 | return {
17 | // set the size of the display
18 | setDisplaySize: setDisplaySize,
19 | // set the size of the map
20 | setMapSize: setMapSize,
21 |
22 | // given a map coordinate, get the position on the canvas
23 | canvasPosition: canvasPosition,
24 |
25 | // from a point on the canvas, get the map position
26 | mapPosition: mapPosition,
27 |
28 | // zoom into a point
29 | zoom: zoom,
30 | unzoom: unzoom,
31 |
32 | // given a player move, scroll to keep the player in view
33 | // returns true if scrolled, so renderers can update
34 | scrollIfNeeded: scrollIfNeeded,
35 |
36 | // boolean: are we zoomed?
37 | zoomed: zoomed,
38 |
39 | // number: pixel size of cells
40 | cellSize: function() { return cellSize },
41 |
42 | // size of the display in pixels
43 | displaySize: function(){ return { width: displayWidth, height: displayHeight }; }
44 | };
45 |
46 | function zoomed() {
47 | return displayCells !== mapWidth;
48 | }
49 |
50 | // zoom the viewport in on a point;
51 | function zoom(center) {
52 | // figure out how many cells wide/high to show
53 | // note it is always an odd number so there is a center cell
54 | // "responsive" -- try to keep the viewport reasonable
55 | var cells = 17;
56 | var smallestScreenDimension = Math.min(displayHeight, displayWidth);
57 |
58 | if (smallestScreenDimension < 600) {
59 | cells = 9;
60 | }
61 | else if (smallestScreenDimension < 1200) {
62 | cells = 13;
63 | }
64 |
65 | // Only zoom if the map is bigger than the screen while zoomed
66 | if (cells > mapWidth) {
67 | unzoom();
68 | return;
69 | }
70 |
71 | displayCells = cells;
72 |
73 | setCenter(center);
74 | setCellSize();
75 | }
76 |
77 | // center the viewpoprt on a maze square
78 | function setCenter(center) {
79 | var offset = Math.floor(displayCells / 2);
80 | cellOffsetX = Math.min(mapWidth - displayCells, Math.max(0, center[0] - offset));
81 | cellOffsetY = Math.min(mapHeight - displayCells, Math.max(0, center[1] - offset));
82 | }
83 |
84 | // undo any zooming
85 | function unzoom() {
86 | displayCells = mapWidth;
87 | cellOffsetX = 0;
88 | cellOffsetY = 0;
89 | setCellSize();
90 | }
91 |
92 |
93 | // when a move happens, decide if we need to scroll and recenter the screen
94 | function scrollIfNeeded(position, direction) {
95 | if (!zoomed()) return;
96 |
97 | if ( direction === 'W' && position[0] <= cellOffsetX && cellOffsetX > 0) {
98 | cellOffsetX = Math.max(0, cellOffsetX - 1);
99 | return true;
100 | }
101 | else if (direction === 'N' && position[1] <= cellOffsetY && cellOffsetY > 0) {
102 | cellOffsetY = Math.max(0, cellOffsetY - 1)
103 | return true;
104 | }
105 | else if (direction === 'E' && position[0] >= cellOffsetX + displayCells - 1 && cellOffsetX < mapHeight - displayCells) {
106 | cellOffsetX = Math.min(mapWidth - displayCells, cellOffsetX + 1);
107 | return true;
108 | }
109 | else if (direction === 'S' && position[1] >= cellOffsetY + displayCells - 1 && cellOffsetY < mapHeight - displayCells) {
110 | cellOffsetY = Math.min(mapHeight - displayCells, cellOffsetY + 1);
111 | return true;
112 | }
113 |
114 | // if avatar was completely offscreen, center on them
115 | if (canvasPosition(position).offscreen) {
116 | setCenter(position);
117 | return true;
118 | }
119 | }
120 |
121 | // When display size changes, recalculate our offsets
122 | function setDisplaySize(width, height) {
123 | displayWidth = width;
124 | displayHeight = height;
125 |
126 | if (width > height) {
127 | displayOffsetX = Math.round((width - height) / 2);
128 | displayOffsetY = 0;
129 | }
130 | else {
131 | displayOffsetX = 0;
132 | displayOffsetY = Math.round((height - width) / 2);
133 | }
134 |
135 | setCellSize();
136 | }
137 |
138 | // Set the size of map.
139 | // This is immediately followed by zooming in practice,
140 | // so we dont' need to do anything else here -- but that could be kind of fragile
141 | function setMapSize(width, height) {
142 | mapWidth = width;
143 | mapHeight = height;
144 | }
145 |
146 | // cache the size of cell; we use it everywhere
147 | function setCellSize() {
148 | // note: only really supports square maps
149 | cellSize = Math.min(displayWidth, displayHeight) / displayCells;
150 | }
151 |
152 | function mapPosition(canvasCoords) {
153 | return [Math.floor((canvasCoords[0] - displayOffsetX) / cellSize) + cellOffsetX, Math.floor((canvasCoords[1] - displayOffsetY) / cellSize) + cellOffsetY];
154 | }
155 |
156 | // canvas positions are used all over the renderers
157 | // to figure out screen coordinates from map coordinates
158 | // they have a bunch of pre-calced props with piel coordinates
159 | // for renderers to use
160 | function canvasPosition(coords) {
161 | var x = coords[0] - cellOffsetX;
162 | var y = coords[1] - cellOffsetY;
163 | var l = Math.floor(x * cellSize + displayOffsetX);
164 | var t = Math.floor(y * cellSize + displayOffsetY);
165 |
166 | return makePosition(l, t, cellSize);
167 | }
168 |
169 | function makePosition(l, t, size) {
170 | var half = Math.floor(size / 2);
171 | var hc = l + half;
172 | var vc = t + half;
173 |
174 | var r = l + size;
175 | var b = t + size;
176 |
177 | // all values are in canvas (ie. pixel) coordinates
178 | var p = {
179 | // numeric values
180 | t: t, // top
181 | l: l, // left
182 | r: r, // right
183 | b: b, // bottom
184 | hc: hc, // horizontal center
185 | vc: vc, // vertical center
186 | size: size, // width/height (it is a square)
187 |
188 | // coordinates for various points in the cell
189 | // [x, y] pairs
190 | center: [hc, vc], // the center
191 |
192 | n: [hc, t], // north: the center point of the north edge
193 | s: [hc, b], // south
194 | e: [r, vc], // east
195 | w: [l, vc], // west
196 |
197 | nw: [l, t], // northwest corner ("top left") of the box
198 | sw: [l, b], // southwest
199 | ne: [r, t], // northeast
200 | se: [r, b], // southeast
201 |
202 | // boolean: is this offscreen (out of the viewport)?
203 | // used to skip rendering
204 | offscreen: l > displayWidth || r < 0 || t > displayHeight || b < 0
205 | };
206 |
207 | // scale method creates another position that has the same center
208 | // but is larger/smaller -- useful for rendering
209 | p.scale = scalePosition.bind(null, p);
210 |
211 | return p;
212 | }
213 |
214 |
215 | // see above - scales a canvas position bigger or smaller
216 | // scale < 1 shrinks, > 1 makes it bigger
217 | function scalePosition(position, scale) {
218 | var size = position.size * scale;
219 | var half = Math.floor(size / 2);
220 | var vc = position.vc;
221 | var hc = position.hc;
222 |
223 | var t = vc - half;
224 | var l = hc - half;
225 | return makePosition(l, t, size);
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/canvas-game/js/display/map-renderer.js:
--------------------------------------------------------------------------------
1 | //
2 |
3 | var MapRenderer = function (
4 | mapContext,
5 | coordinates
6 | ) {
7 | var map = [];
8 | var walls = [];
9 | var corners = [];
10 |
11 | return {
12 | setMap: setMap,
13 | render: render
14 | };
15 |
16 | function setMap(m) {
17 | map = m;
18 | render();
19 | }
20 |
21 | // There are two phases:
22 | // 1. collect all of the elements to render
23 | // 2. render those elements in the proper order from back to front
24 | function render() {
25 | // Collect all the contents we will render
26 | clearContents();
27 | map.forEach(collectRowContents);
28 | collectBoundaries();
29 |
30 | // Now draw
31 | CanvasUtils.resetCanvas(mapContext, Styles.mapBackground);
32 |
33 | // Draw floor
34 | map.forEach(drawRowBackground);
35 |
36 | // Draw walls
37 | renderContents();
38 | }
39 |
40 | function clearContents() {
41 | walls = [];
42 | corners = [];
43 | endcaps = [];
44 | }
45 |
46 |
47 | function collectBoundaries() {
48 | var tl = coordinates.canvasPosition([-1,-1]);
49 | var bl = coordinates.canvasPosition([-1,map.length]);
50 | var tr = coordinates.canvasPosition([map[0].length, -1]);
51 | var br = coordinates.canvasPosition([map[0].length, map.length]);
52 |
53 | addWallSegment(tl.center, bl.center, tl.size);
54 | addWallSegment(tr.center, br.center, tl.size);
55 | addWallSegment(bl.center, br.center, tl.size);
56 | addWallSegment(tl.center, tr.center, tl.size);
57 | }
58 |
59 | function collectRowContents(row, y) {
60 | row.forEach(function (value, x) {
61 | processCell(x, y, value, map);
62 | });
63 | }
64 |
65 | // collect walls
66 | function processCell(x, y, value, map) {
67 | if (value === 'X') {
68 | processFilledCell(x, y, map);
69 | }
70 | }
71 |
72 | // This cell has an X -- figure out how to render the walls
73 | function processFilledCell(x, y, map) {
74 | var cell = coordinates.canvasPosition([x, y]);
75 |
76 | if (cell.offscreen) return;
77 |
78 | var neighbors = getNeighbors(x, y, map);
79 |
80 | addCorners(cell, neighbors);
81 | addWalls([x, y], cell, neighbors);
82 | }
83 |
84 | // Fill in corners with white
85 | function addCorners(cell, neighbors) {
86 | var scaled = cell.scale(1.1);
87 | var half = Math.floor(scaled.size / 2)
88 |
89 | if (neighbors.n && neighbors.w) {
90 | addCorner(scaled.nw, half, half);
91 | }
92 | if (neighbors.n && neighbors.e) {
93 | addCorner(scaled.n, half, half);
94 | }
95 | if (neighbors.s && neighbors.w) {
96 | addCorner(scaled.w, half, half);
97 | }
98 | if (neighbors.s && neighbors.e) {
99 | addCorner(scaled.center, half, half);
100 | }
101 | }
102 |
103 | function addCorner(topLeft, width, height) {
104 | corners.push({
105 | topLeft: topLeft,
106 | width: width,
107 | height: height
108 | });
109 | }
110 |
111 | //
112 | // get the neighbors of the cell.
113 | //
114 | // each direction is either 'X' (wall),
115 | // 'O' (out of bounds), or undefined
116 | function getNeighbors(x, y, map) {
117 | var count = 0;
118 | var edgeCount = 0;
119 |
120 | function neighbor(dx, dy) {
121 | var value = (map[dy + y] || [])[x + dx];
122 |
123 | if (!value) {
124 | // on an edge
125 | edgeCount++;
126 | return 'O';
127 | }
128 | if (value === 'X') {
129 | // neighbor is a wall
130 | count++;
131 | return 'X';
132 | }
133 | }
134 | return {
135 | n: neighbor(0, -1),
136 | s: neighbor(0, 1),
137 | w: neighbor(-1, 0),
138 | e: neighbor(1, 0),
139 | nw: neighbor(-1, -1),
140 | sw: neighbor(-1, 1),
141 | ne: neighbor(1, -1),
142 | se: neighbor(1, 1),
143 | edgeCount: edgeCount,
144 | count: count
145 | };
146 | }
147 |
148 | // add wall to each neighbor that is occupied
149 | function addWalls(position, cell, neighbors) {
150 | addCellWallSegment(cell, neighbors, "n");
151 | addCellWallSegment(cell, neighbors, "w");
152 | addCellWallSegment(cell, neighbors, "s");
153 | addCellWallSegment(cell, neighbors, "e");
154 |
155 | addCellWallSegment(cell, neighbors, "ne");
156 | addCellWallSegment(cell, neighbors, "nw");
157 | addCellWallSegment(cell, neighbors, "se");
158 | addCellWallSegment(cell, neighbors, "sw");
159 |
160 | // don't render endcaps if zoomed out and squares are tiny
161 | if (cell.size < 13) return;
162 |
163 | // add an endcap if this is a lone wall square or
164 | // if there is only one neighbor
165 | if ((neighbors.count === 1 && !neighbors.edgeCount) || neighbors.count === 0) {
166 | // pick a color
167 | var colorIndex = (position[0] + position[1]) % Styles.endcapFill.length;
168 | addEndcap(cell.center, colorIndex);
169 | }
170 | }
171 |
172 | function addEndcap(center, colorIndex) {
173 | endcaps.push({ center: center, colorIndex: colorIndex});
174 | }
175 |
176 | // add a piece of wall if the direction specified by prop is occupied
177 | function addCellWallSegment(cell, neighbors, prop) {
178 | var contents = neighbors[prop];
179 |
180 | if (contents === 'O') {
181 | // If we are at the edge, we don't want to treat the diagonal
182 | // edges as occupied, only the cardinal ones
183 | // so if we are drawing a "nw" wall, only do it if we have north and west neighbors
184 | if (prop.length === 2) {
185 | if (!neighbors[prop[0]] || !neighbors[prop[1]]) {
186 | return;
187 | }
188 | }
189 | // out of bounds means we are at the edge
190 | // scale up so we draw past the edge
191 | var doubled = cell.scale(2);
192 | addWallSegment(doubled[prop], doubled.center, coordinates.cellSize() / 1.7);
193 | }
194 | else if (contents === 'X') {
195 | // fudge enough to always connect adjacent lines
196 | var enlarged = cell.scale(1.1);
197 | addWallSegment(enlarged[prop], cell.center, coordinates.cellSize() / 1.7);
198 | }
199 | }
200 |
201 | function addWallSegment(from, to, thickness) {
202 | walls.push({
203 | from: from, to: to, thickness: thickness
204 | });
205 | }
206 |
207 | // draw the background, a tiled floor pattern
208 | function drawRowBackground(row, y) {
209 | row.forEach(function (value, x) {
210 | drawBackground(mapContext, x, y, value, map);
211 | });
212 | }
213 |
214 | // draw tiles on the entire visible map
215 | function drawBackground(mapContext, x, y) {
216 | var cell = coordinates.canvasPosition([x, y]);
217 | if (cell.offscreen) return;
218 |
219 | var width = cell.size / 40;
220 |
221 | var shrunk = cell.scale(0.95);
222 |
223 | // highlight/shadow
224 | CanvasUtils.drawPath(mapContext, width, Styles.tileOutline[0], [shrunk.sw, shrunk.nw, shrunk.ne]);
225 | CanvasUtils.drawPath(mapContext, width, Styles.tileOutline[1], [shrunk.ne, shrunk.se, shrunk.sw]);
226 | }
227 |
228 | // render the walls, edge boundary, and endcaps
229 | function renderContents() {
230 | // calculate how far to offset our highlight and shadow layers
231 | // that create the faux 3d look based on the cell size.
232 | var dx = Math.min(8, Math.max(2, coordinates.cellSize() / 15));
233 | var dy = dx / 2;
234 | var highlightOffset = [-dx, -dy];
235 | var shadowOffset = [dx, dy];
236 |
237 | // only render the shadow and highlight layers if cells are big enough
238 | if (coordinates.cellSize() > 12) {
239 | // we draw all of the the highlights and shadows on top of each other
240 | endcaps.forEach(drawEndcapBackground.bind(null, highlightOffset, shadowOffset));
241 | walls.forEach(drawWallBackground.bind(null, highlightOffset, shadowOffset));
242 | // border layer
243 | walls.forEach(drawWallLayer1);
244 | }
245 |
246 | // draw the tops of walls ad boundaries
247 | walls.forEach(drawWallLayer2);
248 |
249 | // fill in the corners where needed -
250 | corners.forEach(drawCorner);
251 |
252 | // draw the colored endcaps (if on a large enough screen)
253 | if (coordinates.cellSize() > 12) {
254 | endcaps.forEach(drawEndcap);
255 | }
256 | }
257 |
258 | // draw the highlight and shadow layers
259 | function drawWallBackground(highlightOffset, shadowOffset, wall) {
260 | CanvasUtils.drawPath(mapContext, wall.thickness, Styles.highlight, translateMany(highlightOffset, [wall.from, wall.to]));
261 | CanvasUtils.drawPath(mapContext, wall.thickness, Styles.shadow, translateMany(shadowOffset, [wall.from, wall.to]));
262 | }
263 |
264 | // highlight and shado for the endcap circless
265 | function drawEndcapBackground(highlightOffset, shadowOffset, endcap) {
266 | CanvasUtils.drawFilledCircle(mapContext, translate(highlightOffset, endcap.center), coordinates.cellSize() / 2.5, 2, Styles.highlight, Styles.highlight);
267 | CanvasUtils.drawFilledCircle(mapContext, translate(shadowOffset, endcap.center), coordinates.cellSize() / 2.5, 2, Styles.shadow, Styles.shadow);
268 | }
269 |
270 | // the border layer (black)
271 | function drawWallLayer1(wall) {
272 | CanvasUtils.drawPath(mapContext, wall.thickness, Styles.wallLayer1, [wall.from, wall.to]);
273 | }
274 |
275 | // The top layer (white)
276 | function drawWallLayer2(wall) {
277 | CanvasUtils.drawPath(mapContext, wall.thickness * 0.8, Styles.wallLayer2, [wall.from, wall.to]);
278 | }
279 |
280 | // Corners are just white rects that fill in the corner by a diagonal wall
281 | // so that there is no ugly hole there
282 | function drawCorner(corner) {
283 | mapContext.strokeStyle = Styles.corner;
284 | mapContext.fillStyle = Styles.corner;
285 | mapContext.fillRect(
286 | corner.topLeft[0],
287 | corner.topLeft[1],
288 | corner.width,
289 | corner.height
290 | );
291 | }
292 |
293 | // Endcaps are circles on the end of walls
294 | function drawEndcap(endcap) {
295 | CanvasUtils.drawFilledCircle(mapContext, endcap.center, coordinates.cellSize() / 2.5, coordinates.cellSize() / 12, 'black', 'white');
296 | CanvasUtils.drawFilledCircle(mapContext, endcap.center, coordinates.cellSize() / 3.5, coordinates.cellSize() / 12, 'black', Styles.endcapFill[endcap.colorIndex]);
297 | }
298 |
299 | // utility functions for translating coordinates for highlight and shadow
300 | function translate(offset, pair) {
301 | return [pair[0] + offset[0], pair[1] + offset[1]];
302 | }
303 |
304 | function translateMany(offset, pairs) {
305 | return pairs.map(function(p) { return [p[0] + offset[0], p[1] + offset[1]]; });
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/entries/chapmanj/mazebot-chapmanj-secret.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# 1. Maze Traveler Code"
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": null,
13 | "metadata": {},
14 | "outputs": [],
15 | "source": [
16 | "import urllib, json, urllib.request, numpy as np"
17 | ]
18 | },
19 | {
20 | "cell_type": "code",
21 | "execution_count": null,
22 | "metadata": {},
23 | "outputs": [],
24 | "source": [
25 | "baseurl = \"https://api.noopschallenge.com\""
26 | ]
27 | },
28 | {
29 | "cell_type": "markdown",
30 | "metadata": {},
31 | "source": [
32 | "## 1.1 NOOP Challenge GET and POST functions"
33 | ]
34 | },
35 | {
36 | "cell_type": "code",
37 | "execution_count": null,
38 | "metadata": {},
39 | "outputs": [],
40 | "source": [
41 | "def get_maze(qurl):\n",
42 | " with urllib.request.urlopen(qurl) as url:\n",
43 | " question = json.loads(url.read().decode())\n",
44 | " return question"
45 | ]
46 | },
47 | {
48 | "cell_type": "code",
49 | "execution_count": null,
50 | "metadata": {},
51 | "outputs": [],
52 | "source": [
53 | "def post(qurl, qanswer) :\n",
54 | "\n",
55 | " head={'Content-Type': 'application/json'}\n",
56 | " \n",
57 | " answer = qanswer.encode('utf8')\n",
58 | " \n",
59 | " try :\n",
60 | " req = urllib.request.Request(qurl, data=answer, headers=head)\n",
61 | " res = urllib.request.urlopen(req)\n",
62 | " response = json.load(res)\n",
63 | "\n",
64 | " return response\n",
65 | " \n",
66 | " except urllib.error.HTTPError as e:\n",
67 | " response = json.load(e)\n",
68 | " return response"
69 | ]
70 | },
71 | {
72 | "cell_type": "markdown",
73 | "metadata": {},
74 | "source": [
75 | "## 1.2 Maze Walker Class"
76 | ]
77 | },
78 | {
79 | "cell_type": "code",
80 | "execution_count": null,
81 | "metadata": {},
82 | "outputs": [],
83 | "source": [
84 | "class Walker:\n",
85 | " def __init__(self, x=0, y=0):\n",
86 | " self.x = x\n",
87 | " self.y = y\n",
88 | " self.directions = []\n",
89 | " self.locations = [[x, y]]\n",
90 | " self.visited = [[x, y]]\n",
91 | " \n",
92 | " def xy(self) :\n",
93 | " return [self.x, self.y]\n",
94 | " \n",
95 | " def N(self) : \n",
96 | " self.y -= 1\n",
97 | " self.directions += 'N' \n",
98 | " self.record()\n",
99 | " \n",
100 | " def S(self) :\n",
101 | " self.y += 1\n",
102 | " self.directions += 'S'\n",
103 | " self.record()\n",
104 | "\n",
105 | " def E(self) :\n",
106 | " self.x += 1\n",
107 | " self.directions += 'E'\n",
108 | " self.record()\n",
109 | "\n",
110 | " def W(self) :\n",
111 | " self.x -= 1\n",
112 | " self.directions += 'W'\n",
113 | " self.record()\n",
114 | "\n",
115 | " def set_at(self, index) : \n",
116 | " self.x = self.locations[index][0]\n",
117 | " self.y = self.locations[index][1]\n",
118 | " self.locations = self.locations[0:index + 1]\n",
119 | " self.directions = self.directions[0:index ]\n",
120 | " \n",
121 | " def record(self) :\n",
122 | " self.locations.append([self.x, self.y])\n",
123 | " self.visited.append([self.x, self.y])\n",
124 | "\n",
125 | " def get_moves(self) :\n",
126 | " return [self.N, self.S, self.E, self.W]\n",
127 | " \n",
128 | " def clone(self) :\n",
129 | " myclone = Walker(self.x, self.y)\n",
130 | " \n",
131 | " for i in self.directions :\n",
132 | " myclone.directions.append(i)\n",
133 | " \n",
134 | " for j in self.locations :\n",
135 | " myclone.locations.append([j[0], j[1]])\n",
136 | " \n",
137 | " for k in self.visited :\n",
138 | " myclone.visited.append([k[0], k[1]])\n",
139 | " \n",
140 | " return myclone\n",
141 | " \n",
142 | " def __str__(self) :\n",
143 | " return '(' + str(self.x) + ',' + str(self.y) + ')' + self.directions[-1] #+ self.visited"
144 | ]
145 | },
146 | {
147 | "cell_type": "markdown",
148 | "metadata": {},
149 | "source": [
150 | "## 1.3 Dungeon Master Class"
151 | ]
152 | },
153 | {
154 | "cell_type": "code",
155 | "execution_count": null,
156 | "metadata": {},
157 | "outputs": [],
158 | "source": [
159 | "class Dungeon_master :\n",
160 | " def __init__(self, maze) :\n",
161 | " self.maze = maze #col x row (like x,y)\n",
162 | " \n",
163 | " def solve(self, walker, visualize=False, verbose=False, silent=False, iterations=-1) :\n",
164 | " \n",
165 | " while [walker.x, walker.y] != self.maze['endingPosition'] :\n",
166 | " \n",
167 | " #neighbors ^v><^v><^v><^v><^v><^v><^v><^v><^v><^v><^v><^v><^v><\n",
168 | " #\n",
169 | " \n",
170 | " #move forward >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n",
171 | " #\n",
172 | " \n",
173 | " #backtrack <<<<<<<<<<<<<<<<<<<<<<<<<\n",
174 | " #\n",
175 | " \n",
176 | " #feedback ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
177 | " #\n",
178 | " \n",
179 | " #output ==================================>\n",
180 | " #\n",
181 | " \n",
182 | " return walker.directions\n",
183 | " "
184 | ]
185 | },
186 | {
187 | "cell_type": "markdown",
188 | "metadata": {},
189 | "source": [
190 | "## 1.4 Helper Functions"
191 | ]
192 | },
193 | {
194 | "cell_type": "code",
195 | "execution_count": null,
196 | "metadata": {},
197 | "outputs": [],
198 | "source": [
199 | "def draw_maze(maze_map, walker=None) :\n",
200 | " \n",
201 | " mymap = '\\n'\n",
202 | " \n",
203 | " i = 0\n",
204 | " for row in maze_map:\n",
205 | " j = 0 \n",
206 | " for cell in row :\n",
207 | " if walker != None and [j,i] in walker.locations and cell != 'A' and cell != 'B' : mymap += ' ='\n",
208 | " else: mymap += ' ' + cell \n",
209 | " j += 1\n",
210 | " mymap += '\\n'\n",
211 | " i += 1\n",
212 | " \n",
213 | " print(mymap)"
214 | ]
215 | },
216 | {
217 | "cell_type": "markdown",
218 | "metadata": {},
219 | "source": [
220 | "# 2. Solve a maze"
221 | ]
222 | },
223 | {
224 | "cell_type": "code",
225 | "execution_count": null,
226 | "metadata": {},
227 | "outputs": [],
228 | "source": [
229 | "#get new maze\n",
230 | "#maze = get_maze(baseurl + '/mazebot/random')\n",
231 | "\n",
232 | "#or\n",
233 | "\n",
234 | "#use a single instance\n",
235 | "maze = {'name': 'Maze #472 (20x20)', 'mazePath': '/mazebot/mazes/bVDhG-4JSk4DiK1mJww7uN_hSO2juWMDIox26QVYR8Y', 'startingPosition': [17, 13], 'endingPosition': [12, 9], 'message': 'When you have figured out the solution, post it back to this url in JSON format. See the exampleSolution for more information.', 'exampleSolution': {'directions': 'ENWNNENWNNS'}, 'map': [[' ', ' ', ' ', ' ', ' ', ' ', ' ', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', ' ', ' ', ' ', ' ', ' '], [' ', 'X', 'X', ' ', 'X', ' ', 'X', ' ', 'X', ' ', 'X', 'X', 'X', 'X', 'X', ' ', 'X', ' ', 'X', ' '], [' ', ' ', 'X', ' ', 'X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'X', ' ', ' ', ' ', 'X', ' ', 'X', ' '], [' ', ' ', 'X', ' ', 'X', ' ', 'X', 'X', 'X', 'X', ' ', ' ', ' ', ' ', 'X', ' ', 'X', ' ', 'X', ' '], [' ', 'X', 'X', ' ', 'X', ' ', ' ', ' ', ' ', 'X', 'X', 'X', 'X', ' ', 'X', ' ', 'X', ' ', 'X', ' '], [' ', ' ', ' ', ' ', 'X', ' ', 'X', 'X', ' ', 'X', ' ', ' ', 'X', ' ', 'X', ' ', 'X', ' ', 'X', ' '], [' ', 'X', 'X', 'X', 'X', ' ', 'X', ' ', ' ', 'X', 'X', 'X', 'X', ' ', 'X', ' ', 'X', ' ', 'X', ' '], [' ', 'X', ' ', ' ', 'X', ' ', ' ', 'X', 'X', 'X', ' ', ' ', ' ', ' ', ' ', ' ', 'X', ' ', 'X', ' '], [' ', 'X', 'X', 'X', ' ', 'X', ' ', 'X', ' ', ' ', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', ' '], [' ', ' ', ' ', 'X', ' ', ' ', 'X', ' ', 'X', 'X', 'X', ' ', 'B', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', 'X', ' ', 'X', 'X', 'X', ' ', 'X', ' ', ' ', ' ', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', ' '], [' ', ' ', ' ', ' ', ' ', ' ', 'X', ' ', 'X', 'X', 'X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], ['X', ' ', 'X', 'X', 'X', ' ', 'X', ' ', ' ', ' ', ' ', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'], [' ', 'X', ' ', ' ', ' ', ' ', 'X', ' ', 'X', 'X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'A', ' ', ' '], [' ', 'X', ' ', 'X', 'X', 'X', 'X', ' ', ' ', ' ', 'X', 'X', 'X', 'X', 'X', 'X', 'X', ' ', 'X', ' '], ['X', 'X', ' ', 'X', ' ', ' ', ' ', 'X', 'X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'X', ' ', 'X', ' '], ['X', 'X', ' ', ' ', ' ', 'X', ' ', 'X', ' ', 'X', 'X', 'X', 'X', 'X', 'X', ' ', 'X', ' ', ' ', ' '], ['X', 'X', 'X', 'X', ' ', 'X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'X', 'X', ' ', 'X'], ['X', 'X', 'X', 'X', ' ', ' ', 'X', ' ', 'X', ' ', 'X', ' ', ' ', 'X', ' ', 'X', ' ', ' ', ' ', 'X'], ['X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', ' ', ' ', ' ', 'X', 'X', 'X']]}"
236 | ]
237 | },
238 | {
239 | "cell_type": "code",
240 | "execution_count": null,
241 | "metadata": {
242 | "scrolled": true
243 | },
244 | "outputs": [],
245 | "source": [
246 | "chapj = Walker(maze['startingPosition'][0], maze['startingPosition'][1])\n",
247 | "Yeshua = Dungeon_master(maze)\n",
248 | "maze_tour = Yeshua.solve(chapj, visualize=True, verbose=False, silent=False, iterations=-1)"
249 | ]
250 | },
251 | {
252 | "cell_type": "code",
253 | "execution_count": null,
254 | "metadata": {
255 | "scrolled": true
256 | },
257 | "outputs": [],
258 | "source": [
259 | "answer = '{\"directions\" : \"' + maze_tour + '\"}'\n",
260 | "answer"
261 | ]
262 | },
263 | {
264 | "cell_type": "code",
265 | "execution_count": null,
266 | "metadata": {},
267 | "outputs": [],
268 | "source": [
269 | "mazeboturl = baseurl + maze['mazePath']\n",
270 | "mazeboturl"
271 | ]
272 | },
273 | {
274 | "cell_type": "code",
275 | "execution_count": null,
276 | "metadata": {},
277 | "outputs": [],
278 | "source": [
279 | "response = post(mazeboturl, answer)\n",
280 | "print(response)"
281 | ]
282 | },
283 | {
284 | "cell_type": "markdown",
285 | "metadata": {},
286 | "source": [
287 | "# 3. RaceMode"
288 | ]
289 | },
290 | {
291 | "cell_type": "code",
292 | "execution_count": null,
293 | "metadata": {},
294 | "outputs": [],
295 | "source": [
296 | "response = get_maze('https://api.noopschallenge.com/mazebot/race')\n",
297 | "print(response['message'], response['exampleRequest'])"
298 | ]
299 | },
300 | {
301 | "cell_type": "code",
302 | "execution_count": null,
303 | "metadata": {
304 | "scrolled": true
305 | },
306 | "outputs": [],
307 | "source": [
308 | "def start_race() :\n",
309 | " response = post(baseurl + '/mazebot/race/start', '{\"login\" : \"chapmanj\"}')\n",
310 | "\n",
311 | " while response.get('nextMaze') != None :\n",
312 | "\n",
313 | " #get maze\n",
314 | " maze = get_maze(baseurl + response['nextMaze'])\n",
315 | "\n",
316 | " print('attepting: ' + maze['name'])\n",
317 | "\n",
318 | " #solve it\n",
319 | " chapj = Walker(maze['startingPosition'][0], maze['startingPosition'][1])\n",
320 | " Yeshua = Dungeon_master(maze)\n",
321 | " maze_tour = Yeshua.solve(chapj, visualize=False, verbose=False, silent=True)\n",
322 | " answer = '{\"directions\" : \"' + maze_tour + '\"}'\n",
323 | "\n",
324 | " mazeboturl = baseurl + maze['mazePath']\n",
325 | "\n",
326 | " response = post(mazeboturl, answer)\n",
327 | "\n",
328 | " print(response)\n",
329 | " print('---')"
330 | ]
331 | },
332 | {
333 | "cell_type": "code",
334 | "execution_count": null,
335 | "metadata": {},
336 | "outputs": [],
337 | "source": [
338 | "start_race()"
339 | ]
340 | }
341 | ],
342 | "metadata": {
343 | "kernelspec": {
344 | "display_name": "Python 3",
345 | "language": "python",
346 | "name": "python3"
347 | },
348 | "language_info": {
349 | "codemirror_mode": {
350 | "name": "ipython",
351 | "version": 3
352 | },
353 | "file_extension": ".py",
354 | "mimetype": "text/x-python",
355 | "name": "python",
356 | "nbconvert_exporter": "python",
357 | "pygments_lexer": "ipython3",
358 | "version": "3.7.3"
359 | }
360 | },
361 | "nbformat": 4,
362 | "nbformat_minor": 2
363 | }
364 |
--------------------------------------------------------------------------------
/canvas-game/js/utils/polyfills.js:
--------------------------------------------------------------------------------
1 | (function(undefined) {function Call(t,l){var n=arguments.length>2?arguments[2]:[];if(!1===IsCallable(t))throw new TypeError(Object.prototype.toString.call(t)+"is not a function.");return t.apply(l,n)}function Get(n,t){return n[t]}function HasProperty(n,r){return r in n}function IsCallable(n){return"function"==typeof n}function ToInteger(n){var i=Number(n);return isNaN(i)?0:1/i===Infinity||1/i==-Infinity||i===Infinity||i===-Infinity?i:(i<0?-1:1)*Math.floor(Math.abs(i))}function ToLength(n){var t=ToInteger(n);return t<=0?0:Math.min(t,Math.pow(2,53)-1)}function ToObject(e){if(null===e||e===undefined)throw TypeError();return Object(e)}function GetV(t,e){return ToObject(t)[e]}function GetMethod(e,n){var r=GetV(e,n);if(null===r||r===undefined)return undefined;if(!1===IsCallable(r))throw new TypeError("Method not callable: "+n);return r}function Type(e){switch(typeof e){case"undefined":return"undefined";case"boolean":return"boolean";case"number":return"number";case"string":return"string";case"symbol":return"symbol";default:return null===e?"null":"Symbol"in this&&e instanceof this.Symbol?"symbol":"object"}}function OrdinaryToPrimitive(r,t){if("string"===t)var e=["toString","valueOf"];else e=["valueOf","toString"];for(var i=0;i1?arguments[1]:undefined;if("object"===Type(e)){if(arguments.length<2)var i="default";else t===String?i="string":t===Number&&(i="number");var r="function"==typeof this.Symbol&&"symbol"==typeof this.Symbol.toPrimitive?GetMethod(e,this.Symbol.toPrimitive):undefined;if(r!==undefined){var n=Call(r,e,[i]);if("object"!==Type(n))return n;throw new TypeError("Cannot convert exotic object to primitive.")}return"default"===i&&(i="number"),OrdinaryToPrimitive(e,i)}return e}function ToString(t){switch(Type(t)){case"symbol":throw new TypeError("Cannot convert a Symbol value to a string");case"object":return ToString(ToPrimitive(t,"string"));default:return String(t)}}if (!("document"in this
2 | )) {"undefined"==typeof WorkerGlobalScope&&"function"!=typeof importScripts&&(this.HTMLDocument?this.Document=this.HTMLDocument:(this.Document=this.HTMLDocument=document.constructor=new Function("return function Document() {}")(),this.Document.prototype=document));}if (!("Element"in this&&"HTMLElement"in this
3 | )) {!function(){function e(){return a--||clearTimeout(t),!(!document.body||document.body.prototype||!/(complete|interactive)/.test(document.readyState))&&(m(document,!0),t&&document.body.prototype&&clearTimeout(t),!!document.body.prototype)}if(window.Element&&!window.HTMLElement)return void(window.HTMLElement=window.Element);window.Element=window.HTMLElement=new Function("return function Element() {}")();var t,n=document.appendChild(document.createElement("body")),o=n.appendChild(document.createElement("iframe")),r=o.contentWindow.document,c=Element.prototype=r.appendChild(r.createElement("*")),d={},m=function(e,t){var n,o,r,c=e.childNodes||[],u=-1;if(1===e.nodeType&&e.constructor!==Element){e.constructor=Element;for(n in d)o=d[n],e[n]=o}for(;r=t&&c[++u];)m(r,t);return e},u=document.getElementsByTagName("*"),i=document.createElement,a=100;c.attachEvent("onpropertychange",function(e){for(var t,n=e.propertyName,o=!d.hasOwnProperty(n),r=c[n],m=d[n],i=-1;t=u[++i];)1===t.nodeType&&(o||t[n]===m)&&(t[n]=r);d[n]=r}),c.constructor=Element,c.hasAttribute||(c.hasAttribute=function l(e){return null!==this.getAttribute(e)}),e()||(document.onreadystatechange=e,t=setInterval(e,25)),document.createElement=function p(e){var t=i(String(e).toLowerCase());return m(t)},document.removeChild(n)}();}if (!("defineProperty"in Object&&function(){try{var e={}
4 | return Object.defineProperty(e,"test",{value:42}),!0}catch(t){return!1}}()
5 | )) {!function(e){var t=Object.prototype.hasOwnProperty("__defineGetter__"),r="A property cannot both have accessors and be writable or have a value";Object.defineProperty=function n(o,i,c){if(e&&(o===window||o===document||o===Element.prototype||o instanceof Element))return e(o,i,c);if(null===o||!(o instanceof Object||"object"==typeof o))throw new TypeError("Object.defineProperty called on non-object");if(!(c instanceof Object))throw new TypeError("Property description must be an object");var a=String(i),f="value"in c||"writable"in c,p="get"in c&&typeof c.get,s="set"in c&&typeof c.set;if(p){if("function"!==p)throw new TypeError("Getter must be a function");if(!t)throw new TypeError("Getters & setters cannot be defined on this javascript engine");if(f)throw new TypeError(r);Object.__defineGetter__.call(o,a,c.get)}else o[a]=c.value;if(s){if("function"!==s)throw new TypeError("Setter must be a function");if(!t)throw new TypeError("Getters & setters cannot be defined on this javascript engine");if(f)throw new TypeError(r);Object.__defineSetter__.call(o,a,c.set)}return"value"in c&&(o[a]=c.value),o}}(Object.defineProperty);}function CreateMethodProperty(e,r,t){var a={value:t,writable:!0,enumerable:!1,configurable:!0};Object.defineProperty(e,r,a)}if (!("forEach"in Array.prototype
6 | )) {CreateMethodProperty(Array.prototype,"forEach",function r(t){var e=ToObject(this),n=e instanceof String?e.split(""):e,o=ToLength(Get(e,"length"));if(!1===IsCallable(t))throw new TypeError(t+" is not a function");for(var a=arguments.length>1?arguments[1]:undefined,i=0;in&&(r.length=n)}var r=A(n),o=0;return function(n,t){r[o++]=n,r[o++]=t,2===o&&rn.nextTick(e)}}function l(n,t){var e,r,u,c,f=0;if(!n)throw h(Q);var a=n[rn[q][z]];if(o(a))r=a.call(n);else{if(!o(n.next)){if(i(n,A)){for(e=n.length;f-1?e:t}function l(t,e){e=e||{};var r=e.body;if(t instanceof l){if(t.bodyUsed)throw new TypeError("Already read");this.url=t.url,this.credentials=t.credentials,e.headers||(this.headers=new n(t.headers)),this.method=t.method,this.mode=t.mode,r||null==t._bodyInit||(r=t._bodyInit,t.bodyUsed=!0)}else this.url=String(t);if(this.credentials=e.credentials||this.credentials||"omit",!e.headers&&this.headers||(this.headers=new n(e.headers)),this.method=y(e.method||this.method||"GET"),this.mode=e.mode||this.mode||null,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&r)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(r)}function p(t){var e=new FormData;return t.trim().split("&").forEach(function(t){if(t){var r=t.split("="),o=r.shift().replace(/\+/g," "),n=r.join("=").replace(/\+/g," ");e.append(decodeURIComponent(o),decodeURIComponent(n))}}),e}function c(t){var e=new n;return t.replace(/\r?\n[\t ]+/g," ").split(/\r?\n/).forEach(function(t){var r=t.split(":"),o=r.shift().trim();if(o){var n=r.join(":").trim();e.append(o,n)}}),e}function b(t,e){e||(e={}),this.type="default",this.status=e.status===undefined?200:e.status,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in e?e.statusText:"OK",this.headers=new n(e.headers),this.url=e.url||"",this._initBody(t)}var m={searchParams:"URLSearchParams"in t,iterable:"Symbol"in t&&"iterator"in Symbol,blob:"FileReader"in t&&"Blob"in t&&function(){try{return new Blob,!0}catch(t){return!1}}(),formData:"FormData"in t,arrayBuffer:"ArrayBuffer"in t};if(m.arrayBuffer)var w=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],v=function(t){return t&&DataView.prototype.isPrototypeOf(t)},B=ArrayBuffer.isView||function(t){return t&&w.indexOf(Object.prototype.toString.call(t))>-1};n.prototype.append=function(t,o){t=e(t),o=r(o);var n=this.map[t];this.map[t]=n?n+","+o:o},n.prototype["delete"]=function(t){delete this.map[e(t)]},n.prototype.get=function(t){return t=e(t),this.has(t)?this.map[t]:null},n.prototype.has=function(t){return this.map.hasOwnProperty(e(t))},n.prototype.set=function(t,o){this.map[e(t)]=r(o)},n.prototype.forEach=function(t,e){for(var r in this.map)this.map.hasOwnProperty(r)&&t.call(e,this.map[r],r,this)},n.prototype.keys=function(){var t=[];return this.forEach(function(e,r){t.push(r)}),o(t)},n.prototype.values=function(){var t=[];return this.forEach(function(e){t.push(e)}),o(t)},n.prototype.entries=function(){var t=[];return this.forEach(function(e,r){t.push([r,e])}),o(t)},m.iterable&&(n.prototype[Symbol.iterator]=n.prototype.entries);var _=["DELETE","GET","HEAD","OPTIONS","POST","PUT"];l.prototype.clone=function(){return new l(this,{body:this._bodyInit})},d.call(l.prototype),d.call(b.prototype),b.prototype.clone=function(){return new b(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new n(this.headers),url:this.url})},b.error=function(){var t=new b(null,{status:0,statusText:""});return t.type="error",t};var A=[301,302,303,307,308];b.redirect=function(t,e){if(-1===A.indexOf(e))throw new RangeError("Invalid status code");return new b(null,{status:e,headers:{location:t}})},t.Headers=n,t.Request=l,t.Response=b,t.fetch=function(t,e){return new Promise(function(r,o){var n=new l(t,e),i=new XMLHttpRequest;i.onload=function(){var t={status:i.status,statusText:i.statusText,headers:c(i.getAllResponseHeaders()||"")};t.url="responseURL"in i?i.responseURL:t.headers.get("X-Request-URL");var e="response"in i?i.response:i.responseText;r(new b(e,t))},i.onerror=function(){o(new TypeError("Network request failed"))},i.ontimeout=function(){o(new TypeError("Network request failed"))},i.open(n.method,n.url,!0),"include"===n.credentials?i.withCredentials=!0:"omit"===n.credentials&&(i.withCredentials=!1),"responseType"in i&&m.blob&&(i.responseType="blob"),n.headers.forEach(function(t,e){i.setRequestHeader(e,t)}),i.send("undefined"==typeof n._bodyInit?null:n._bodyInit)})},t.fetch.polyfill=!0}("undefined"!=typeof self?self:this);}}).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
14 |
--------------------------------------------------------------------------------