Also, here are some dancing robots! I stole them from here. Like many people have said, "Good artists copy; great artists steal."
20 |
21 |
22 |
23 |
24 |
25 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/day11/drawclickygrid.js:
--------------------------------------------------------------------------------
1 | function drawGrid(gameBoard) {
2 | // set grid rows and columns and the size of each square
3 | var rows = 10;
4 | var cols = 10;
5 | var squareSize = 50;
6 | // make the grid columns and rows
7 | for (i = 0; i < cols; i++) {
8 | for (j = 0; j < rows; j++) {
9 |
10 | // create a new HTML element for each grid square and make it the right size
11 | var square = document.createElement("div");
12 | gameBoard.appendChild(square);
13 |
14 | square.id = 'square-' + j + i;
15 |
16 | // set each grid square's coordinates: multiples of the current row or column number
17 | var topPosition = j * squareSize;
18 | var leftPosition = i * squareSize;
19 |
20 | // use CSS absolute positioning to place each grid square on the page
21 | square.style.top = topPosition + 'px';
22 | square.style.left = leftPosition + 'px';
23 | }
24 | }
25 | }
26 |
27 | // actually draw the grid
28 | var gameBoard = document.getElementById("gameboard");
29 | drawGrid(gameBoard);
30 |
31 | // set event listener for all elements in gameboard
32 | gameBoard.addEventListener("click", makeAlert, false);
33 |
34 | // create an alert box saying which element was clicked
35 | // code via http://www.kirupa.com/html5/handling_events_for_many_elements.htm
36 | function makeAlert(e) {
37 | if (e.target !== e.currentTarget) {
38 | //var clickedItem = e.target.id;
39 | //alert("Clicked on " + clickedItem);
40 | e.target.style.background = 'red';
41 | }
42 | e.stopPropagation();
43 | }
44 |
--------------------------------------------------------------------------------
/day11/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 30 Days of Web Dev - Day 11 - The Game is Afoot
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
20 |
The Game is Afoot
21 |
22 |
For a while I was building a simple Battleship game as part of an ongoing workshop I did with my Learn to Code LA meetup group, but I never got very far with it because I was too busy teaching! So today I thought I'd use what I learned about JavaScript, HTML and CSS to start building a more polished version of that game.
27 |
28 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/day11/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 60px auto;
3 | width: 70%;
4 | max-width: 950px;
5 | }
6 | nav ul, footer ul {
7 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
8 | padding: 0px;
9 | list-style: none;
10 | font-weight: bold;
11 | }
12 | nav ul li, footer ul li {
13 | display: inline;
14 | margin-right: 20px;
15 | }
16 | a {
17 | text-decoration: none;
18 | color: #999;
19 | }
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 | h1 {
24 | font-size: 3em;
25 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
26 | }
27 | p, button, input {
28 | font-size: 1.5em;
29 | line-height: 1.4em;
30 | color: #333;
31 | margin-bottom:1em;
32 | }
33 | footer {
34 | clear:both;
35 | border-top: 1px solid #d5d5d5;
36 | font-size: .8em;
37 | }
38 |
39 | ul.posts {
40 | margin: 20px auto 40px;
41 | font-size: 1.5em;
42 | }
43 |
44 | ul.posts li {
45 | list-style: none;
46 | }
47 |
48 | #gameboard {
49 | position:relative;
50 | margin:0 auto 2em auto;
51 | width:500px;
52 | height:500px;
53 | }
54 |
55 | #gameboard div {
56 | position:absolute;
57 | -webkit-box-sizing: border-box; /* Safari 3.0 - 5.0, Chrome 1 - 9, Android 2.1 - 3.x */
58 | -moz-box-sizing: border-box; /* Firefox 1 - 28 */
59 | box-sizing: border-box; /* Safari 5.1+, Chrome 10+, Firefox 29+, Opera 7+, IE 8+, Android 4.0+, iOS any */
60 | background: #f6f8f9; /* Old browsers */
61 | border: 1px solid #ddd;
62 | width:50px;
63 | height:50px;
64 | }
--------------------------------------------------------------------------------
/day12/battleship.js:
--------------------------------------------------------------------------------
1 | function drawGrid(gameBoardContainer) {
2 | // set grid rows and columns and the size of each square
3 | var rows = 10;
4 | var cols = 10;
5 | var squareSize = 50;
6 | // make the grid columns and rows
7 | for (i = 0; i < cols; i++) {
8 | for (j = 0; j < rows; j++) {
9 |
10 | // create a new HTML element for each grid square and make it the right size
11 | var square = document.createElement("div");
12 | gameBoardContainer.appendChild(square);
13 |
14 | square.id = 's' + j + i;
15 |
16 | // set each grid square's coordinates: multiples of the current row or column number
17 | var topPosition = j * squareSize;
18 | var leftPosition = i * squareSize;
19 |
20 | // use CSS absolute positioning to place each grid square on the page
21 | square.style.top = topPosition + 'px';
22 | square.style.left = leftPosition + 'px';
23 | }
24 | }
25 | }
26 |
27 | // create the array that will contain the status of each square on the board
28 | // and place ships on the board (later, create function for random placement!)
29 | // 0 = empty, 1 = part of a ship, 2 = a sunken part of a ship, 3 = a missed shot
30 | var gameBoard = [
31 | [0,0,0,1,1,1,1,0,0,0],
32 | [0,0,0,0,0,0,0,0,0,0],
33 | [0,0,0,0,0,0,0,0,0,0],
34 | [0,0,0,0,0,0,1,0,0,0],
35 | [0,0,0,0,0,0,1,0,0,0],
36 | [1,0,0,0,0,0,1,1,1,1],
37 | [1,0,0,0,0,0,0,0,0,0],
38 | [1,0,0,1,0,0,0,0,0,0],
39 | [1,0,0,1,0,0,0,0,0,0],
40 | [1,0,0,0,0,0,0,0,0,0]
41 | ]
42 | // lazy way of tracking when the game is won: just increment hitCount on every hit
43 | var hitCount = 0;
44 |
45 | // actually draw the grid
46 | var gameBoardContainer = document.getElementById("gameboard");
47 | drawGrid(gameBoardContainer);
48 |
49 | // set event listener for all elements in gameboard, run firTorpedo function when square is clicked
50 | gameBoardContainer.addEventListener("click", fireTorpedo, false);
51 |
52 | // initial code via http://www.kirupa.com/html5/handling_events_for_many_elements.htm:
53 | function fireTorpedo(e) {
54 | // if item clicked (e.target) is not the parent element on which the event listener was set (e.currentTarget)
55 | if (e.target !== e.currentTarget) {
56 | // extract row and column # from the HTML element's id
57 | var row = e.target.id.substring(1,2);
58 | var col = e.target.id.substring(2,3);
59 | //alert("Clicked on row " + row + ", col " + col);
60 |
61 | // if player clicks a square with no ship, change the color and change square's value
62 | if (gameBoard[row][col] == 0) {
63 | e.target.style.background = '#bbb';
64 | // set this square's value to 3 to indicate that they fired and missed
65 | gameBoard[row][col] = 3;
66 |
67 | // if player clicks a square with a ship, change the color and change square's value
68 | } else if (gameBoard[row][col] == 1) {
69 | e.target.style.background = 'red';
70 | // set this square's value to 2 to indicate the ship has been hit
71 | gameBoard[row][col] = 2;
72 |
73 | // increment hitCount each time a ship is hit
74 | hitCount++;
75 | // this definitely shouldn't be hard-coded, but here it is anyway. lazy, simple solution:
76 | if (hitCount == 17) {
77 | alert("All enemy battleships have been defeated! You win!");
78 | }
79 |
80 | // if player clicks a square that's been previously hit, let them know
81 | } else if (gameBoard[row][col] > 1) {
82 | alert("Stop wasting your torpedos! You already fired at this location.");
83 | }
84 | }
85 | e.stopPropagation();
86 | }
--------------------------------------------------------------------------------
/day12/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 30 Days of Web Dev - Day 12 - Battleship
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
20 |
Battleship
21 |
22 |
Continuing from where I left off yesterday, I got a working (but very boring) Battleship game! My favorite part was making the two-dimensional array in JavaScript. Before adding any more features, I'll make it the object-oriented way. Click the grid to fire at my fleet of battleships!
23 |
24 |
25 |
26 |
27 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/day12/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 60px auto;
3 | width: 70%;
4 | max-width: 950px;
5 | }
6 | nav ul, footer ul {
7 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
8 | padding: 0px;
9 | list-style: none;
10 | font-weight: bold;
11 | }
12 | nav ul li, footer ul li {
13 | display: inline;
14 | margin-right: 20px;
15 | }
16 | a {
17 | text-decoration: none;
18 | color: #999;
19 | }
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 | h1 {
24 | font-size: 3em;
25 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
26 | }
27 | p, button, input {
28 | font-size: 1.5em;
29 | line-height: 1.4em;
30 | color: #333;
31 | margin-bottom:1em;
32 | }
33 | footer {
34 | clear:both;
35 | border-top: 1px solid #d5d5d5;
36 | font-size: .8em;
37 | }
38 |
39 | ul.posts {
40 | margin: 20px auto 40px;
41 | font-size: 1.5em;
42 | }
43 |
44 | ul.posts li {
45 | list-style: none;
46 | }
47 |
48 | #gameboard {
49 | position:relative;
50 | margin:0 auto 2em auto;
51 | width:500px;
52 | height:500px;
53 | }
54 |
55 | #gameboard div {
56 | position:absolute;
57 | -webkit-box-sizing: border-box; /* Safari 3.0 - 5.0, Chrome 1 - 9, Android 2.1 - 3.x */
58 | -moz-box-sizing: border-box; /* Firefox 1 - 28 */
59 | box-sizing: border-box; /* Safari 5.1+, Chrome 10+, Firefox 29+, Opera 7+, IE 8+, Android 4.0+, iOS any */
60 | background: #f6f8f9; /* Old browsers */
61 | border: 1px solid #ddd;
62 | width:50px;
63 | height:50px;
64 | }
--------------------------------------------------------------------------------
/day13/battleship.js:
--------------------------------------------------------------------------------
1 | // this constructor function defines the BattleshipGame class, which runs the whole game
2 | function BattleshipGame(rows, cols, squareSize, gameUIContainer) {
3 | this.rows = rows;
4 | this.cols = cols;
5 | this.squareSize = squareSize;
6 | this.board = [];
7 | this.ships = [];
8 |
9 | // set up the empty game board and draw the UI
10 | for (i = 0; i < this.cols; i++) {
11 | // create empty 2d array
12 | this.board.push([]);
13 | for (j = 0; j < this.rows; j++) {
14 | // initialize game board 2d array with 0s
15 | this.board[i].push(0);
16 | // create a new HTML element for each grid square and make it the right size
17 | var square = document.createElement("div");
18 | gameUIContainer.appendChild(square);
19 |
20 | // set each element's id to the format 's01'
21 | // *** note that things will break if using more than 10 rows or cols! (accessed later using substring function assuming single digit values for row and col)
22 | square.id = 's' + j + i;
23 |
24 | // use CSS absolute positioning to place each grid square on the page
25 | square.style.top = (j * this.squareSize) + 'px';
26 | square.style.left = (i * this.squareSize) + 'px';
27 |
28 | // set square size
29 | square.style.width = this.squareSize + 'px';
30 | square.style.height = this.squareSize + 'px';
31 |
32 | // set gameUIcontainer size based on square size, rows and cols
33 | gameUIContainer.style.width = (this.squareSize * this.cols) + 'px';
34 | gameUIContainer.style.height = (this.squareSize * this.rows) + 'px';
35 | }
36 | }
37 |
38 | // method for placing a ship in a random location
39 | // *** still need to figure out the math/logic! just one square for now:
40 | this.placeRandomShip = function(ship) {
41 | // pick random starting coordinates (limited by # of rows/cols)
42 | var x = Math.floor(Math.random() * this.rows);
43 | var y = Math.floor(Math.random() * this.cols);
44 |
45 | // save coords in ship object
46 | ship.coords[0] = [x,y];
47 | // change color in game board UI
48 | document.getElementById('s'+x+y).style.background = 'red';
49 | };
50 |
51 | }
52 |
53 | // constructor for the Ship class
54 | function Ship(size) {
55 | this.damage = 0;
56 | this.coords = [];
57 |
58 | // 2d coords array will contain [x,y] coordinate pairs for each square occupied (# of squares determined by size)
59 | for (i = 0; i < size; i++) {
60 | this.coords.push([]);
61 | }
62 | }
63 |
64 | // run this function when user clicks "Randomly Place Ship" button
65 | function createAndPlaceShip() {
66 | // only run this function if game object already exists
67 | if (typeof game != 'undefined') {
68 | // ships have no size yet, they're all just 1 square! but I left this here for later
69 | var size = parseInt(document.getElementById('shipsize').value);
70 |
71 | if (size < game.rows) {
72 | // create a new ship, add it to game.ships array
73 | var ship = new Ship(size);
74 | game.ships.push(ship);
75 | // run function that generates ship's coordinates and displays it on the game board
76 | game.placeRandomShip(ship);
77 | console.log(game.ships);
78 | } else {
79 | alert('Invalid ship size. Enter a number from 1 to ' + game.rows);
80 | }
81 | } else {
82 | alert('Create a grid first!');
83 | }
84 | }
85 |
86 | var game; // this will be used to hold the game object
87 |
88 | function setup() {
89 | //reset gridcontainer each time button is pressed
90 | document.getElementById('gameboard').innerHTML = '';
91 |
92 | // get user input
93 | var rows = parseInt(document.getElementById('numrows').value, 10);
94 | var cols = parseInt(document.getElementById('numcols').value, 10);
95 |
96 | // create and setup the game board
97 | game = new BattleshipGame(rows, cols, 50, document.getElementById("gameboard"));
98 | }
99 |
100 | // add event listeners to buttons
101 | document.getElementById('startbtn').addEventListener('click', setup);
102 | document.getElementById('placeship').addEventListener('click', createAndPlaceShip);
103 |
--------------------------------------------------------------------------------
/day13/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 30 Days of Web Dev - Day 13 - Battleship Objects
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
20 |
31 |
32 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/day13/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 60px auto;
3 | width: 70%;
4 | max-width: 950px;
5 | }
6 | nav ul, footer ul {
7 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
8 | padding: 0px;
9 | list-style: none;
10 | font-weight: bold;
11 | }
12 | nav ul li, footer ul li {
13 | display: inline;
14 | margin-right: 20px;
15 | }
16 | a {
17 | text-decoration: none;
18 | color: #999;
19 | }
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 | h1 {
24 | font-size: 3em;
25 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
26 | }
27 | p, button, input {
28 | font-size: 1.5em;
29 | line-height: 1.4em;
30 | color: #333;
31 | margin-bottom:1em;
32 | }
33 | footer {
34 | clear:both;
35 | border-top: 1px solid #d5d5d5;
36 | font-size: .8em;
37 | }
38 |
39 | ul.posts {
40 | margin: 20px auto 40px;
41 | font-size: 1.5em;
42 | }
43 |
44 | ul.posts li {
45 | list-style: none;
46 | }
47 |
48 | #gameboard {
49 | position:relative;
50 | margin:0 auto 2em auto;
51 | width:500px;
52 | height:500px;
53 | }
54 |
55 | #gameboard div {
56 | position:absolute;
57 | -webkit-box-sizing: border-box; /* Safari 3.0 - 5.0, Chrome 1 - 9, Android 2.1 - 3.x */
58 | -moz-box-sizing: border-box; /* Firefox 1 - 28 */
59 | box-sizing: border-box; /* Safari 5.1+, Chrome 10+, Firefox 29+, Opera 7+, IE 8+, Android 4.0+, iOS any */
60 | background: #f6f8f9; /* Old browsers */
61 | border: 1px solid #ddd;
62 | }
63 |
64 | #btns {
65 | text-align:center;
66 | }
--------------------------------------------------------------------------------
/day14/battleship.js:
--------------------------------------------------------------------------------
1 | // this constructor function defines the BattleshipGame class, which runs the whole game
2 | function BattleshipGame(rows, cols, squareSize, gameUIContainer) {
3 | this.rows = rows;
4 | this.cols = cols;
5 | this.squareSize = squareSize;
6 | this.board = [];
7 | this.ships = [];
8 |
9 | // set up the empty game board and draw the UI
10 | for (i = 0; i < this.rows; i++) {
11 | // create empty 2d array
12 | this.board.push([]);
13 | for (j = 0; j < this.cols; j++) {
14 | // initialize game board 2d array with 0s
15 | this.board[i].push(0);
16 | // create a new HTML element for each grid square and make it the right size
17 | var square = document.createElement("div");
18 | gameUIContainer.appendChild(square);
19 |
20 | // set each element's id to the format 's01'
21 | square.id = 's' + i + '-' + j;
22 |
23 | // use CSS absolute positioning to place each grid square on the page
24 | square.style.top = (i * this.squareSize) + 'px';
25 | square.style.left = (j * this.squareSize) + 'px';
26 |
27 | // set square size
28 | square.style.width = this.squareSize + 'px';
29 | square.style.height = this.squareSize + 'px';
30 |
31 | // set gameUIcontainer size based on square size, rows and cols
32 | gameUIContainer.style.width = (this.squareSize * this.cols) + 'px';
33 | gameUIContainer.style.height = (this.squareSize * this.rows) + 'px';
34 | }
35 | }
36 |
37 | // method for placing a ship in a random location
38 | this.placeRandomShip = function(ship, maxAttempts) {
39 | console.log('Attempting to place a ship of size ' + ship.coords.length);
40 | // pick random starting coordinates (limited by # of rows/cols)
41 | var x = Math.floor(Math.random() * this.rows);
42 | var y = Math.floor(Math.random() * this.cols);
43 | // pick random orientation, either 0 or 1 (0 = horizontal, 1 = vertical)
44 | var orientation = Math.floor(Math.random() * 2);
45 | console.log('Starting coords: ' + x + ', ' + y + '\nOrientation: ' + (orientation ? 'vertical' : 'horizontal'));
46 |
47 | // create a temporary ship in which to test generated coords; only place ship if there's room for it!
48 | var testShip = ship;
49 | for (i = 0; i < testShip.coords.length; i++) {
50 | // if current x, y coords exist on the board and the square is empty (contains a 0):
51 | if (typeof this.board[x] != 'undefined' && typeof this.board[x][y] != 'undefined' && this.board[x][y] == 0) {
52 | // save current coords to temporary ship
53 | testShip.coords[i] = [x, y];
54 | console.log('Trying testShip.coords[' + i + '] = ' + ship.coords[i]);
55 | if (orientation == 0) {
56 | y++; //increment y coord to generate a ship oriented horizontally
57 | } else {
58 | x++; // increment x coord for vertical ships
59 | }
60 | } else if (maxAttempts > 0) { // if this spot goes off the grid or a ship already exists there, try again until maxAttemps is reached:
61 | console.log("Ship can't be placed. maxAttempts = " + maxAttempts);
62 | // delete this ship!
63 | // decrement maxAttempts and call this function to try placing the ship again
64 | maxAttempts--;
65 | this.placeRandomShip(ship, maxAttempts);
66 | return;
67 | // everything after here in the placeRandomShip() function should only execute if there's room for the ship:
68 |
69 | } else { // if ship can't be placed and maxAttempts has been reached, tell the user to try again:
70 | console.log("Ship can't be placed. maxAttempts = " + maxAttempts);
71 | alert("Ship could not be placed. If there isn't room, try placing a smaller ship.");
72 | return;
73 | }
74 | }
75 | console.log('Determined that this ship will fit on the board.');
76 | // save ship's coordinates to ship object and board object
77 | ship = testShip;
78 | // pick a random color for each ship. code via http://stackoverflow.com/a/5092872
79 | var randomHexColor = "#000000".replace(/0/g,function(){return (~~(Math.random()*16)).toString(16);});
80 | // place ship on board object, represented by a 1 for each of the ship's coordinate locations
81 | for (i = 0; i < ship.coords.length; i++) {
82 | var x = ship.coords[i][0];
83 | var y = ship.coords[i][1];
84 | this.board[x][y] = 1;
85 | // display ship on game board UI
86 | document.getElementById('s'+x+'-'+y).style.background = randomHexColor;
87 | console.log('Placed ship square ' + i + ' at ' + x + ', ' + y)
88 | }
89 | console.log('Ship of size ' + ship.coords.length + ' successfully placed on the board!')
90 | };
91 |
92 | }
93 |
94 | // constructor for the Ship class
95 | function Ship(size) {
96 | this.damage = 0;
97 | this.coords = [];
98 |
99 | // 2d coords array will contain [x,y] coordinate pairs for each square occupied (# of squares determined by size)
100 | for (i = 0; i < size; i++) {
101 | this.coords.push([]);
102 | }
103 | }
104 |
105 | // run this function when user clicks "Randomly Place Ship" button
106 | function createAndPlaceShip() {
107 | // only run this function if game object already exists
108 | if (typeof game != 'undefined') {
109 | // ships have no size yet, they're all just 1 square! but I left this here for later
110 | var size = parseInt(document.getElementById('shipsize').value);
111 |
112 | if (size < game.rows) {
113 | // create a new ship, add it to game.ships array
114 | var ship = new Ship(size);
115 | game.ships.push(ship);
116 | // run function that generates ship's coordinates and displays it on the game board
117 | game.placeRandomShip(ship, 10);
118 | console.log('Number of ships on the board: ' + game.ships.length)
119 | console.log(game.ships);
120 | } else {
121 | alert('Invalid ship size. Enter a number from 1 to ' + game.rows);
122 | }
123 | } else {
124 | alert('Create a grid first!');
125 | }
126 | }
127 |
128 | var game; // this will be used to hold the game object
129 |
130 | function setup() {
131 | //reset gridcontainer each time button is pressed
132 | document.getElementById('gameboard').innerHTML = '';
133 |
134 | // get user input
135 | var rows = parseInt(document.getElementById('numrows').value, 10);
136 | var cols = parseInt(document.getElementById('numcols').value, 10);
137 |
138 | // create and setup the game board
139 | game = new BattleshipGame(rows, cols, 50, document.getElementById("gameboard"));
140 | console.log('Set up new game with a ' + rows + ' by ' + cols + ' board.');
141 | console.log(game);
142 | }
143 |
144 | // add event listeners to buttons
145 | document.getElementById('startbtn').addEventListener('click', setup);
146 | document.getElementById('placeship').addEventListener('click', createAndPlaceShip);
147 |
--------------------------------------------------------------------------------
/day14/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 30 Days of Web Dev - Day 14 - Recursive Randomness
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
20 |
Recursive Randomness
21 |
22 |
Building on yesterday's randomly-placed single squares, today I figured out how to randomly place battleships of various sizes on my grid! Now I can prevent them from overlapping, too! Not sure if I did it "right", but it seems to be working. This StackOverflow thread was really helpful.
23 |
And apparently I made a recursive function. Don't think I've ever done that before!
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/day14/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 60px auto;
3 | width: 70%;
4 | max-width: 950px;
5 | }
6 | nav ul, footer ul {
7 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
8 | padding: 0px;
9 | list-style: none;
10 | font-weight: bold;
11 | }
12 | nav ul li, footer ul li {
13 | display: inline;
14 | margin-right: 20px;
15 | }
16 | a {
17 | text-decoration: none;
18 | color: #999;
19 | }
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 | h1 {
24 | font-size: 3em;
25 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
26 | }
27 | p, button, input {
28 | font-size: 1.5em;
29 | line-height: 1.4em;
30 | color: #333;
31 | margin-bottom:1em;
32 | }
33 | footer {
34 | clear:both;
35 | border-top: 1px solid #d5d5d5;
36 | font-size: .8em;
37 | }
38 |
39 | ul.posts {
40 | margin: 20px auto 40px;
41 | font-size: 1.5em;
42 | }
43 |
44 | ul.posts li {
45 | list-style: none;
46 | }
47 |
48 | #gameboard {
49 | position:relative;
50 | margin:0 auto 2em auto;
51 | width:500px;
52 | height:500px;
53 | }
54 |
55 | #gameboard div {
56 | position:absolute;
57 | -webkit-box-sizing: border-box; /* Safari 3.0 - 5.0, Chrome 1 - 9, Android 2.1 - 3.x */
58 | -moz-box-sizing: border-box; /* Firefox 1 - 28 */
59 | box-sizing: border-box; /* Safari 5.1+, Chrome 10+, Firefox 29+, Opera 7+, IE 8+, Android 4.0+, iOS any */
60 | background: #f6f8f9; /* Old browsers */
61 | border: 1px solid #ddd;
62 | }
63 |
64 | #btns {
65 | text-align:center;
66 | }
--------------------------------------------------------------------------------
/day15/battleship.js:
--------------------------------------------------------------------------------
1 | // this constructor function defines the BattleshipGame class, which runs the whole game
2 | function BattleshipGame(rows, cols, squareSize, gameUIContainer) {
3 | this.rows = rows;
4 | this.cols = cols;
5 | this.squareSize = squareSize;
6 | this.board = [];
7 | this.ships = [];
8 | this.hitCount = 0;
9 | this.missedCount = 0;
10 |
11 | // set up the empty game board and draw the UI
12 | for (i = 0; i < this.rows; i++) {
13 | // create empty 2d array
14 | this.board.push([]);
15 | for (j = 0; j < this.cols; j++) {
16 | // initialize game board 2d array with 0s
17 | this.board[i].push(0);
18 | // create a new HTML element for each grid square and make it the right size
19 | var square = document.createElement("div");
20 | gameUIContainer.appendChild(square);
21 |
22 | // set each element's id to the format 's01'
23 | square.id = 's' + i + '-' + j;
24 |
25 | // use CSS absolute positioning to place each grid square on the page
26 | square.style.top = (i * this.squareSize) + 'px';
27 | square.style.left = (j * this.squareSize) + 'px';
28 |
29 | // set square size
30 | square.style.width = this.squareSize + 'px';
31 | square.style.height = this.squareSize + 'px';
32 |
33 | // set gameUIcontainer size based on square size, rows and cols
34 | gameUIContainer.style.width = (this.squareSize * this.cols) + 'px';
35 | gameUIContainer.style.height = (this.squareSize * this.rows) + 'px';
36 | }
37 | }
38 |
39 | // create and randomly place a ship (calls placeRandomShip function)
40 | this.createShip = function(size) {
41 | // quick lazy solution so the ship will definitely fit on the game board
42 | // TODO: generate ship sizes based on game board size or user inputs
43 | if (size > this.rows) {
44 | size = this.rows;
45 | }
46 | if (size > this.cols) {
47 | size = this.cols;
48 | }
49 | // create a new ship, add it to game.ships array
50 | var ship = new Ship(size);
51 | this.ships.push(ship);
52 | // run function that generates ship's coordinates and displays it on the game board
53 | this.placeRandomShip(ship, 50);
54 | console.log('Number of ships on the board: ' + this.ships.length)
55 | console.log(this.ships);
56 | };
57 |
58 | // method for placing a ship in a random location
59 | this.placeRandomShip = function(ship, maxAttempts) {
60 | console.log('Attempting to place a ship of size ' + ship.coords.length);
61 | // pick random starting coordinates (limited by # of rows/cols)
62 | var x = Math.floor(Math.random() * this.rows);
63 | var y = Math.floor(Math.random() * this.cols);
64 | // pick random orientation, either 0 or 1 (0 = horizontal, 1 = vertical)
65 | var orientation = Math.floor(Math.random() * 2);
66 | console.log('Starting coords: ' + x + ', ' + y + '\nOrientation: ' + (orientation ? 'vertical' : 'horizontal'));
67 |
68 | // create a temporary ship in which to test generated coords; only place ship if there's room for it!
69 | var testShip = ship;
70 | for (i = 0; i < testShip.coords.length; i++) {
71 | // if current x, y coords exist on the board and the square is empty (contains a 0):
72 | if (typeof this.board[x] != 'undefined' && typeof this.board[x][y] != 'undefined' && this.board[x][y] == 0) {
73 | // save current coords to temporary ship
74 | testShip.coords[i] = [x, y];
75 | console.log('Trying testShip.coords[' + i + '] = ' + ship.coords[i]);
76 | if (orientation == 0) {
77 | y++; //increment y coord to generate a ship oriented horizontally
78 | } else {
79 | x++; // increment x coord for vertical ships
80 | }
81 | } else if (maxAttempts > 0) { // if this spot goes off the grid or a ship already exists there, try again until maxAttemps is reached:
82 | console.log("Ship can't be placed. maxAttempts = " + maxAttempts);
83 | // delete this ship!
84 | // decrement maxAttempts and call this function to try placing the ship again
85 | maxAttempts--;
86 | this.placeRandomShip(ship, maxAttempts);
87 | return;
88 | // everything after here in the placeRandomShip() function should only execute if there's room for the ship:
89 |
90 | } else { // if ship can't be placed and maxAttempts has been reached, tell the user to try again:
91 | console.log("Ship can't be placed. maxAttempts = " + maxAttempts);
92 | alert("Ship could not be placed. If there isn't room, try placing a smaller ship.");
93 | return;
94 | }
95 | }
96 | console.log('Determined that this ship will fit on the board.');
97 | // save ship's coordinates to ship object and board object
98 | ship = testShip;
99 | // place ship on board object, represented by a 1 for each of the ship's coordinate locations
100 | for (i = 0; i < ship.coords.length; i++) {
101 | var x = ship.coords[i][0];
102 | var y = ship.coords[i][1];
103 | this.board[x][y] = 1;
104 | console.log('Placed ship square ' + i + ' at ' + x + ', ' + y)
105 | }
106 | console.log('Ship of size ' + ship.coords.length + ' successfully placed on the board!')
107 | };
108 | }
109 |
110 | // constructor for the Ship class
111 | function Ship(size) {
112 | this.damage = 0;
113 | this.coords = [];
114 | // pick a random color for each ship. code via http://stackoverflow.com/a/5092872
115 | this.color = "#000000".replace(/0/g,function(){return (~~(Math.random()*16)).toString(16);});
116 |
117 | // 2d coords array will contain [x,y] coordinate pairs for each square occupied (# of squares determined by size)
118 | for (i = 0; i < size; i++) {
119 | this.coords.push([]);
120 | }
121 | }
122 |
123 | // global game variable, which will be modified by other functions
124 | var game;
125 |
126 | function setupGame(e) {
127 | // get game board container element
128 | var gameBoardContainer = document.getElementById('gameboard');
129 | // clear the game board (for when resetting)
130 | gameBoardContainer.innerHTML = '';
131 | // create and setup the game board
132 | game = new BattleshipGame(10, 10, 50, gameBoardContainer);
133 | console.log('Set up new game with a ' + game.rows + ' by ' + game.cols + ' board.');
134 | console.log(game);
135 |
136 | // create and randomly place some ships
137 | game.createShip(2);
138 | game.createShip(3);
139 | game.createShip(3);
140 | game.createShip(4);
141 | game.createShip(5);
142 |
143 | // update the start button text to say restart
144 | e.target.innerHTML = 'Restart Game';
145 | document.getElementById('hits').innerHTML = 'Click the board to fire!';
146 | // set event listener for all elements in gameboard, run fireTorpedo function when square is clicked
147 | gameBoardContainer.addEventListener("click", fireTorpedo, false);
148 | }
149 |
150 | // run this function when user clicks on the game board
151 | // initial code via http://www.kirupa.com/html5/handling_events_for_many_elements.htm
152 | function fireTorpedo(e) {
153 | // if item clicked (e.target) is not the parent element on which the event listener was set (e.currentTarget)
154 | if (e.target !== e.currentTarget) {
155 | // extract row and column # from the HTML element's id
156 | // TODO: rewrite this for dynamic board size with more than 10 columns
157 | var row = e.target.id.substring(1,2);
158 | var col = e.target.id.substring(3,4);
159 |
160 | // if player clicks a square with no ship, change the color and change square's value
161 | if (game.board[row][col] == 0) {
162 | e.target.style.background = '#bbb';
163 | // set this square's value to 3 to indicate that they fired and missed
164 | game.board[row][col] = 3;
165 | game.missedCount++;
166 |
167 | // if player clicks a square with a ship, change the color and change square's value
168 | } else if (game.board[row][col] == 1) {
169 | e.target.style.background = 'red';
170 | // set this square's value to 2 to indicate the ship has been hit
171 | game.board[row][col] = 2;
172 | // increment hitCount each time a ship is hit
173 | game.hitCount++;
174 | // TODO: update hitCount dynamically based on the ships on the board
175 | if (game.hitCount == 17) {
176 | alert("All enemy battleships have been defeated! You win!");
177 | }
178 |
179 | // if player clicks a square that's been previously hit, let them know
180 | } else if (game.board[row][col] > 1) {
181 | alert("Stop wasting your torpedos! You already fired at this location.");
182 | }
183 | // update game score in HTML
184 | document.getElementById('hits').innerHTML = 'Hits: ' + game.hitCount + ' | Misses: ' + game.missedCount;
185 | }
186 | e.stopPropagation();
187 | }
188 |
189 | function displayShips() {
190 | for (i = 0; i < game.ships.length; i++) {
191 | for (j = 0; j < game.ships[i].coords.length; j++) {
192 | var x = game.ships[i].coords[j][0];
193 | var y = game.ships[i].coords[j][1];
194 |
195 | if (game.board[x][y] == 2) {
196 | document.getElementById('s'+x+'-'+y).style.background = 'red';
197 | } else if (game.board[x][y] == 3) {
198 | document.getElementById('s'+x+'-'+y).style.background = '#bbb';
199 | } else {
200 | document.getElementById('s'+x+'-'+y).style.background = game.ships[i].color;
201 | }
202 | }
203 | }
204 | }
205 |
206 | // set event listeners so clicking buttons will run functions:
207 | document.getElementById('startbtn').addEventListener('click', setupGame);
208 | document.getElementById('displayships').addEventListener('click', displayShips);
--------------------------------------------------------------------------------
/day15/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 30 Days of Web Dev - Day 15 - Working Battleship
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
20 |
Working Battleship
21 |
22 |
Today I combined my code from the last couple of days to (almost) finish my Battleship game!
23 |
24 |
25 |
26 |
27 |
28 |
29 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/day15/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 60px auto;
3 | width: 70%;
4 | max-width: 950px;
5 | }
6 | nav ul, footer ul {
7 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
8 | padding: 0px;
9 | list-style: none;
10 | font-weight: bold;
11 | }
12 | nav ul li, footer ul li {
13 | display: inline;
14 | margin-right: 20px;
15 | }
16 | a {
17 | text-decoration: none;
18 | color: #999;
19 | }
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 | h1 {
24 | font-size: 3em;
25 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
26 | }
27 | p, button, input {
28 | font-size: 1.5em;
29 | line-height: 1.4em;
30 | color: #333;
31 | margin-bottom:1em;
32 | }
33 | footer {
34 | clear:both;
35 | border-top: 1px solid #d5d5d5;
36 | font-size: .8em;
37 | }
38 |
39 | ul.posts {
40 | margin: 20px auto 40px;
41 | font-size: 1.5em;
42 | }
43 |
44 | ul.posts li {
45 | list-style: none;
46 | }
47 |
48 | #gameboard {
49 | position:relative;
50 | margin:0 auto 2em auto;
51 | width:500px;
52 | height:500px;
53 | }
54 |
55 | #gameboard div {
56 | position:absolute;
57 | -webkit-box-sizing: border-box; /* Safari 3.0 - 5.0, Chrome 1 - 9, Android 2.1 - 3.x */
58 | -moz-box-sizing: border-box; /* Firefox 1 - 28 */
59 | box-sizing: border-box; /* Safari 5.1+, Chrome 10+, Firefox 29+, Opera 7+, IE 8+, Android 4.0+, iOS any */
60 | background: #f6f8f9; /* Old browsers */
61 | border: 1px solid #ddd;
62 | }
63 |
64 | #btns {
65 | text-align:center;
66 | }
--------------------------------------------------------------------------------
/day17/battleship.js:
--------------------------------------------------------------------------------
1 | // this constructor function defines the BattleshipGame class, which runs the whole game
2 | function BattleshipGame(rows, cols, gameBoardContainer, alertsContainer) {
3 | this.board = [];
4 | this.ships = [];
5 | this.hitCount = 0;
6 | this.missedCount = 0;
7 | this.shipsRemaining = 0;
8 |
9 | // a helper object for sending alerts to the user
10 | function GameAlerts (alertsContainer) {
11 | this.displayMessage = function (newMessage) {
12 | alertsContainer.innerHTML = newMessage;
13 | };
14 | }
15 |
16 | this.alerts = new GameAlerts(alertsContainer);
17 |
18 | // set size of each grid square
19 | var squareSize = 50;
20 |
21 | // set up the empty game board and draw the UI
22 | for (i = 0; i < rows; i++) {
23 | // create empty 2d array
24 | this.board.push([]);
25 | for (j = 0; j < cols; j++) {
26 | // initialize game board 2d array with 0s
27 | this.board[i].push(0);
28 | // create a new HTML element for each grid square and make it the right size
29 | var square = document.createElement("div");
30 | gameBoardContainer.appendChild(square);
31 |
32 | // set each element's id to the format 's01'
33 | square.id = 's' + i + '-' + j;
34 |
35 | // use CSS absolute positioning to place each grid square on the page
36 | square.style.top = (i * squareSize) + 'px';
37 | square.style.left = (j * squareSize) + 'px';
38 |
39 | // set square size
40 | square.style.width = squareSize + 'px';
41 | square.style.height = squareSize + 'px';
42 |
43 | // set gameBoardContainer size based on square size, rows and cols
44 | gameBoardContainer.style.width = (squareSize * cols) + 'px';
45 | gameBoardContainer.style.height = (squareSize * rows) + 'px';
46 | }
47 | }
48 |
49 | // create and randomly place a ship (calls placeRandomShip function)
50 | this.createShip = function(size) {
51 | // quick lazy solution so the ship will definitely fit on the game board
52 | // TODO: generate ship sizes based on game board size or user inputs
53 | if (size > this.board.length) {
54 | size = this.board.length;
55 | }
56 | if (size > this.board[0].length) {
57 | size = this.board[0].length;
58 | }
59 | // create a new ship, add it to game.ships array
60 | var ship = new Ship(size);
61 | this.ships.push(ship);
62 | this.shipsRemaining++;
63 | // run function that generates ship's coordinates and displays it on the game board
64 | this.placeRandomShip(ship, 50);
65 | console.log('Number of ships on the board: ' + this.ships.length)
66 | console.log(this.ships);
67 | };
68 |
69 | // method for placing a ship in a random location
70 | this.placeRandomShip = function(ship, maxAttempts) {
71 | console.log('Attempting to place a ship of size ' + ship.coords.length);
72 | // pick random starting coordinates (limited by # of rows/cols)
73 | var x = Math.floor(Math.random() * this.board.length);
74 | var y = Math.floor(Math.random() * this.board[0].length);
75 | // pick random orientation, either 0 or 1 (0 = horizontal, 1 = vertical)
76 | var orientation = Math.floor(Math.random() * 2);
77 | console.log('Starting coords: ' + x + ', ' + y + '\nOrientation: ' + (orientation ? 'vertical' : 'horizontal'));
78 |
79 | // create a temporary ship in which to test generated coords; only place ship if there's room for it!
80 | var testShip = ship;
81 | for (i = 0; i < testShip.coords.length; i++) {
82 | // if current x, y coords exist on the board and the square is empty (contains a 0):
83 | if (typeof this.board[x] != 'undefined' && typeof this.board[x][y] != 'undefined' && this.board[x][y] == 0) {
84 | // save current coords to temporary ship
85 | testShip.coords[i] = [x, y];
86 | console.log('Trying testShip.coords[' + i + '] = ' + ship.coords[i]);
87 | if (orientation == 0) {
88 | y++; //increment y coord to generate a ship oriented horizontally
89 | } else {
90 | x++; // increment x coord for vertical ships
91 | }
92 | } else if (maxAttempts > 0) { // if this spot goes off the grid or a ship already exists there, try again until maxAttemps is reached:
93 | console.log("Ship can't be placed. maxAttempts = " + maxAttempts);
94 | // delete this ship!
95 | // decrement maxAttempts and call this function to try placing the ship again
96 | maxAttempts--;
97 | this.placeRandomShip(ship, maxAttempts);
98 | return;
99 | // everything after here in the placeRandomShip() function should only execute if there's room for the ship:
100 |
101 | } else { // if ship can't be placed and maxAttempts has been reached, tell the user to try again:
102 | console.log("Ship can't be placed. maxAttempts = " + maxAttempts);
103 | alert("Ship could not be placed. If there isn't room, try placing a smaller ship.");
104 | return;
105 | }
106 | }
107 | console.log('Determined that this ship will fit on the board.');
108 | // save ship's coordinates to ship object and board object
109 | ship = testShip;
110 | // place ship on board object, represented by a 1 for each of the ship's coordinate locations
111 | for (i = 0; i < ship.coords.length; i++) {
112 | var x = ship.coords[i][0];
113 | var y = ship.coords[i][1];
114 | this.board[x][y] = 1;
115 | console.log('Placed ship square ' + i + ' at ' + x + ', ' + y)
116 | }
117 | console.log('Ship of size ' + ship.coords.length + ' successfully placed on the board!')
118 | };
119 |
120 | this.displayShips = function() {
121 | for (i = 0; i < this.ships.length; i++) {
122 | for (j = 0; j < this.ships[i].coords.length; j++) {
123 | var x = this.ships[i].coords[j][0];
124 | var y = this.ships[i].coords[j][1];
125 |
126 | if (this.board[x][y] == 2) {
127 | document.getElementById('s'+x+'-'+y).style.background = 'red';
128 | } else if (this.board[x][y] == 3) {
129 | document.getElementById('s'+x+'-'+y).style.background = '#bbb';
130 | } else {
131 | document.getElementById('s'+x+'-'+y).style.background = this.ships[i].color;
132 | }
133 | }
134 | }
135 | };
136 |
137 | // return the ship object that occupies the given coordinate on the game board
138 | this.getShipByCoordinate = function(x, y) {
139 | for (i = 0; i < this.ships.length; i++) {
140 | for (j = 0; j < this.ships[i].coords.length; j++) {
141 | if (this.ships[i].coords[j][0] == x && this.ships[i].coords[j][1] == y) {
142 | // if the coordinate belongs to the current ship, return this ship
143 | return this.ships[i];
144 | } // otherwise, keep searching until finding a match
145 | }
146 | }
147 | // if all ships have been checked and no match, return false to indicate no ship has these coordinates
148 | return false;
149 | };
150 |
151 | // prevent user from clicking the board after game is over
152 | this.end = function() {
153 | gameBoardContainer.removeEventListener("click", onBoardClick, false);
154 | };
155 |
156 | // run this method when user clicks on the game board
157 | // boilerplate code via http://www.kirupa.com/html5/handling_events_for_many_elements.htm
158 | this.fireTorpedo = function (e) {
159 | var newMessage = '';
160 | // if item clicked (e.target) is not the parent element on which the event listener was set (e.currentTarget)
161 | if (e.target !== e.currentTarget) {
162 | // extract row and column # from the HTML element's id
163 | // TODO: rewrite this for dynamic board size with more than 10 columns
164 | var row = e.target.id.substring(1,2);
165 | var col = e.target.id.substring(3,4);
166 | console.log('Firing torpedo!');
167 |
168 | // if player clicks a square with no ship, change the color and change square's value
169 | if (this.board[row][col] == 0) {
170 | e.target.style.background = '#bbb';
171 | // set this square's value to 3 to indicate that they fired and missed
172 | this.board[row][col] = 3;
173 | this.missedCount++;
174 | console.log('Missed!');
175 | newMessage = ' Missed!';
176 |
177 | // if player clicks a square with a ship, change the color and change square's value
178 | } else if (this.board[row][col] == 1) {
179 | e.target.style.background = 'red';
180 | // set this square's value to 2 to indicate the ship has been hit
181 | this.board[row][col] = 2;
182 | // increment hitCount each time a ship is hit
183 | this.hitCount++;
184 |
185 | var currentShip = this.getShipByCoordinate(row, col);
186 | var isShipSunk = currentShip.takeDamage(row, col);
187 | if (isShipSunk) {
188 | newMessage = ' Battleship sunk!';
189 | this.shipsRemaining--;
190 | } else {
191 | newMessage = ' You hit a ship!';
192 | }
193 | console.log('Hit! Ships remaining: ' + this.shipsRemaining);
194 | console.log('isShipSunk: ' + isShipSunk);
195 | console.log('currentShip.damage: ' + currentShip.damage);
196 | if (this.shipsRemaining == 0) {
197 | newMessage = " All enemy battleships have been defeated! You win!";
198 | this.end(); // end the game, don't allow any more clicks on the board
199 | }
200 | // if player clicks a square that's been previously hit, let them know
201 | } else if (this.board[row][col] > 1) {
202 | newMessage = ' Stop wasting your torpedos! You already fired at this location.';
203 | }
204 | // update game score in HTML
205 | this.alerts.displayMessage('Hits: ' + this.hitCount + ' | Misses: ' + this.missedCount + ' | Ships remaining: ' + this.shipsRemaining + newMessage + '');
206 | }
207 | e.stopPropagation();
208 | };
209 | }
210 |
211 | // constructor for the Ship class
212 | function Ship(size) {
213 | this.damage = 0;
214 | this.coords = [];
215 | // pick a random color for each ship. code via http://stackoverflow.com/a/5092872
216 | this.color = "#000000".replace(/0/g,function(){return (~~(Math.random()*16)).toString(16);});
217 |
218 | // 2d coords array will contain [x,y] coordinate pairs for each square occupied (# of squares determined by size)
219 | for (i = 0; i < size; i++) {
220 | this.coords.push([]);
221 | }
222 |
223 | // update ship's damage count and return true if ship has been sunk
224 | this.takeDamage = function (x, y) {
225 | for (i = 0; i < this.coords.length; i++) {
226 | if (this.coords[i][0] == x && this.coords[i][1] == y) {
227 | this.damage++;
228 | console.log('Ship took damage at ' + x + ', ' + y + '. Damage: ' + this.damage);
229 | }
230 | }
231 | if (this.damage == this.coords.length) {
232 | return true; // ship has been sunk!
233 | } else {
234 | return false;
235 | }
236 | };
237 | }
238 |
239 | // global game variable, which will be modified by other functions
240 | var game;
241 |
242 | // function to use in addEventListener -- it needed a name so I can later use removeEventHandler
243 | // there's probably a better way to do this, which I'll look into later
244 | var onBoardClick = function (e) {
245 | return game.fireTorpedo(e);
246 | };
247 |
248 | function setupGame(e) {
249 | // get game board container element
250 | var gameBoardContainer = document.getElementById('gameboard');
251 | // clear the game board (for when resetting)
252 | gameBoardContainer.innerHTML = '';
253 | // create and setup the game board
254 | game = new BattleshipGame(10, 10, gameBoardContainer, document.getElementById('message'));
255 | console.log('Set up new game with a ' + game.board.length + ' by ' + game.board[0].length + ' board.');
256 | console.log(game);
257 |
258 | // create and randomly place some ships
259 | game.createShip(2);
260 | game.createShip(3);
261 | game.createShip(3);
262 | game.createShip(4);
263 | game.createShip(5);
264 |
265 | // update the start button text to say restart
266 | e.target.innerHTML = 'Restart Game';
267 | game.alerts.displayMessage('Click the board to fire!');
268 | // set event listener for all elements in gameboard, run fireTorpedo function when square is clicked
269 | gameBoardContainer.addEventListener("click", onBoardClick, false);
270 | }
271 |
272 | // set event listeners so clicking buttons will run functions:
273 | document.getElementById('startbtn').addEventListener('click', setupGame);
274 | document.getElementById('displayships').addEventListener('click', function () {game.displayShips();});
--------------------------------------------------------------------------------
/day17/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 30 Days of Web Dev - Day 17 - Basic Battleship Completed!
5 |
6 |
7 |
8 |
9 |
16 |
Basic Battleship Completed!
17 |
18 |
Finally! Version 1 is done! Hmm, now for version 2...
I spent the whole day today at Rails Girls LA, learning about Ruby on Rails with about 100 women and lots of great coaches! (See the new GitHub repository I made today for my example project.) I figured eight hours of that should count for my daily web dev learning challenge!
19 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/day18/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 60px auto;
3 | width: 70%;
4 | max-width: 950px;
5 | }
6 | nav ul, footer ul {
7 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
8 | padding: 0px;
9 | list-style: none;
10 | font-weight: bold;
11 | }
12 | nav ul li, footer ul li {
13 | display: inline;
14 | margin-right: 20px;
15 | }
16 | a {
17 | text-decoration: none;
18 | color: #999;
19 | }
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 | h1 {
24 | font-size: 3em;
25 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
26 | }
27 | p, button, input {
28 | font-size: 1.5em;
29 | line-height: 1.4em;
30 | color: #333;
31 | margin-bottom:1em;
32 | }
33 | footer {
34 | clear:both;
35 | border-top: 1px solid #d5d5d5;
36 | font-size: .8em;
37 | }
38 |
39 | ul.posts {
40 | margin: 20px auto 40px;
41 | font-size: 1.5em;
42 | }
43 |
44 | ul.posts li {
45 | list-style: none;
46 | }
47 |
48 | #gameboard {
49 | position:relative;
50 | margin:0 auto 2em auto;
51 | }
52 |
53 | #gameboard div {
54 | position:absolute;
55 | -webkit-box-sizing: border-box; /* Safari 3.0 - 5.0, Chrome 1 - 9, Android 2.1 - 3.x */
56 | -moz-box-sizing: border-box; /* Firefox 1 - 28 */
57 | box-sizing: border-box; /* Safari 5.1+, Chrome 10+, Firefox 29+, Opera 7+, IE 8+, Android 4.0+, iOS any */
58 | background: #f6f8f9; /* Old browsers */
59 | border: 1px solid #ddd;
60 | }
61 |
62 | #btns, #message {
63 | text-align:center;
64 | }
65 |
66 | #message {
67 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
68 | color: #333;
69 | }
--------------------------------------------------------------------------------
/day2/fizzbuzz.js:
--------------------------------------------------------------------------------
1 | // this variable will keep track of the current number
2 | var i = 0;
3 |
4 | function getFizzBuzzy() {
5 |
6 | // increment i by 1 each time the function runs
7 | i++;
8 |
9 | // create an object for the list element:
10 | var newItem = document.createElement("li");
11 |
12 | // put the list element inside the HTML element with ID "fizzbuzzlist"
13 | document.getElementById("fizzbuzzlist").appendChild(newItem);
14 |
15 | // set a fresh, empty string to append to each time the function runs
16 | var fizzBuzzString = '';
17 |
18 | // if the current number i is neither divisible by 3 nor divisible by 5
19 | if ( (i % 3 != 0) && (i % 5 != 0) ) {
20 | // append i to the string
21 | fizzBuzzString += i;
22 | }
23 |
24 | // if the current number i is divisible by 3 (has no remainder when divded by 3)
25 | if (i % 3 == 0) {
26 |
27 | // append Fizz to the string
28 | fizzBuzzString += 'Fizz';
29 |
30 | // set this list item's class="fizz " (note the empty space at the end!)
31 | newItem.className = "fizz ";
32 | }
33 |
34 | // do the same thing but with "buzz" if i is divisible by 5
35 | if (i % 5 == 0) {
36 | fizzBuzzString += 'Buzz';
37 |
38 | // set this list item's class="buzz" (or "fizz buzz" if i is also divisible by 3)
39 | newItem.className += "buzz";
40 | }
41 |
42 | // create a text node object containing the fizzbuzz text
43 | var text = document.createTextNode(fizzBuzzString);
44 |
45 | // put the fizzbuzz text inside the list element:
fizzbuzz goes in here
46 | newItem.appendChild(text);
47 | }
48 |
49 | // run getFizzBuzzy function when button with ID "btn" is pressed
50 | document.getElementById('btn').addEventListener('click', getFizzBuzzy);
--------------------------------------------------------------------------------
/day2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 30 Days of Web Dev - Day 2 - Fizz Buzz
5 |
6 |
7 |
8 |
9 |
16 |
Fizz Buzz
17 |
Today I made sure I could solve fizz buzz, a common practice problem used in programming interviews. Learn more about it here, here, and here.
18 |
19 |
20 |
21 |
22 |
23 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/day2/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 60px auto;
3 | width: 70%;
4 | max-width: 950px;
5 | }
6 | nav ul, footer ul {
7 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
8 | padding: 0px;
9 | list-style: none;
10 | font-weight: bold;
11 | }
12 | nav ul li, footer ul li {
13 | display: inline;
14 | margin-right: 20px;
15 | }
16 | a {
17 | text-decoration: none;
18 | color: #999;
19 | }
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 | h1 {
24 | font-size: 3em;
25 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
26 | }
27 | p, button {
28 | font-size: 1.5em;
29 | line-height: 1.4em;
30 | color: #333;
31 | margin-bottom:1em;
32 | }
33 | footer {
34 | clear:both;
35 | border-top: 1px solid #d5d5d5;
36 | font-size: .8em;
37 | }
38 |
39 | ul.posts {
40 | margin: 20px auto 40px;
41 | font-size: 1.5em;
42 | }
43 |
44 | ul.posts li {
45 | list-style: none;
46 | }
47 |
48 | ul#fizzbuzzlist {
49 | list-style: none;
50 | font-size: 1em;
51 | color: #333;
52 | margin: 0 auto;
53 | }
54 |
55 | ul#fizzbuzzlist li {
56 | display: inline;
57 | float:left;
58 | margin: 5px;
59 | padding: 20px;
60 | width:50px;
61 | height:50px;
62 | text-align:center;
63 | background:#d5d5d5;
64 | }
65 |
66 | ul#fizzbuzzlist li.buzz{
67 | background: #99ccee;
68 | }
69 |
70 | ul#fizzbuzzlist li.fizz{
71 | background:#eecc99;
72 | }
73 | ul#fizzbuzzlist li.fizz.buzz {
74 | background-image: linear-gradient(45deg, #eecc99 50%, #99ccee 50%);
75 | background-image: -o-linear-gradient(45deg, #eecc99 50%, #99ccee 50%);
76 | background-image: -moz-linear-gradient(45deg, #eecc99 50%, #99ccee 50%);
77 | background-image: -webkit-linear-gradient(45deg, #eecc99 50%, #99ccee 50%);
78 | background-image: -ms-linear-gradient(45deg, #eecc99 50%, #99ccee 50%);
79 | }
--------------------------------------------------------------------------------
/day20/battleship.js:
--------------------------------------------------------------------------------
1 | // this constructor function defines the BattleshipGame class, which runs the whole game
2 | function BattleshipGame(rows, cols, gameBoardContainer, alertsContainer) {
3 | this.board = [];
4 | this.ships = [];
5 | this.hitCount = 0;
6 | this.missedCount = 0;
7 | this.shipsRemaining = 0;
8 |
9 | // a helper object for sending alerts to the user
10 | function GameAlerts (alertsContainer) {
11 | this.displayMessage = function (newMessage) {
12 | alertsContainer.innerHTML = newMessage;
13 | };
14 | }
15 |
16 | this.alerts = new GameAlerts(alertsContainer);
17 |
18 | // set size of each grid square
19 | var squareSize = 50;
20 |
21 | // set up the empty game board and draw the UI
22 | for (i = 0; i < rows; i++) {
23 | // create empty 2d array
24 | this.board.push([]);
25 | for (j = 0; j < cols; j++) {
26 | // initialize game board 2d array with 0s
27 | this.board[i].push(0);
28 | // create a new HTML element for each grid square and make it the right size
29 | var square = document.createElement("div");
30 | gameBoardContainer.appendChild(square);
31 |
32 | // set each element's id to the format 's01'
33 | square.id = 's' + i + '-' + j;
34 |
35 | // use CSS absolute positioning to place each grid square on the page
36 | square.style.top = (i * squareSize) + 'px';
37 | square.style.left = (j * squareSize) + 'px';
38 |
39 | // set square size
40 | square.style.width = squareSize + 'px';
41 | square.style.height = squareSize + 'px';
42 |
43 | // set gameBoardContainer size based on square size, rows and cols
44 | gameBoardContainer.style.width = (squareSize * cols) + 'px';
45 | gameBoardContainer.style.height = (squareSize * rows) + 'px';
46 | }
47 | }
48 |
49 | // create and randomly place a ship (calls placeRandomShip function)
50 | this.createShip = function(size) {
51 | // quick lazy solution so the ship will definitely fit on the game board
52 | // TODO: generate ship sizes based on game board size or user inputs
53 | if (size > this.board.length) {
54 | size = this.board.length;
55 | }
56 | if (size > this.board[0].length) {
57 | size = this.board[0].length;
58 | }
59 | // create a new ship, add it to game.ships array
60 | var ship = new Ship(size);
61 | this.ships.push(ship);
62 | this.shipsRemaining++;
63 | // run function that generates ship's coordinates and displays it on the game board
64 | this.placeRandomShip(ship, 50);
65 | console.log('Number of ships on the board: ' + this.ships.length)
66 | console.log(this.ships);
67 | };
68 |
69 | // method for placing a ship in a random location
70 | this.placeRandomShip = function(ship, maxAttempts) {
71 | console.log('Attempting to place a ship of size ' + ship.coords.length);
72 | // pick random starting coordinates (limited by # of rows/cols)
73 | var x = Math.floor(Math.random() * this.board.length);
74 | var y = Math.floor(Math.random() * this.board[0].length);
75 | // pick random orientation, either 0 or 1 (0 = horizontal, 1 = vertical)
76 | var orientation = Math.floor(Math.random() * 2);
77 | console.log('Starting coords: ' + x + ', ' + y + '\nOrientation: ' + (orientation ? 'vertical' : 'horizontal'));
78 |
79 | // create a temporary ship in which to test generated coords; only place ship if there's room for it!
80 | var testShip = ship;
81 | for (i = 0; i < testShip.coords.length; i++) {
82 | // if current x, y coords exist on the board and the square is empty (contains a 0):
83 | if (typeof this.board[x] != 'undefined' && typeof this.board[x][y] != 'undefined' && this.board[x][y] == 0) {
84 | // save current coords to temporary ship
85 | testShip.coords[i] = [x, y];
86 | console.log('Trying testShip.coords[' + i + '] = ' + ship.coords[i]);
87 | if (orientation == 0) {
88 | y++; //increment y coord to generate a ship oriented horizontally
89 | } else {
90 | x++; // increment x coord for vertical ships
91 | }
92 | } else if (maxAttempts > 0) { // if this spot goes off the grid or a ship already exists there, try again until maxAttemps is reached:
93 | console.log("Ship can't be placed. maxAttempts = " + maxAttempts);
94 | // delete this ship!
95 | // decrement maxAttempts and call this function to try placing the ship again
96 | maxAttempts--;
97 | this.placeRandomShip(ship, maxAttempts);
98 | return;
99 | // everything after here in the placeRandomShip() function should only execute if there's room for the ship:
100 |
101 | } else { // if ship can't be placed and maxAttempts has been reached, tell the user to try again:
102 | console.log("Ship can't be placed. maxAttempts = " + maxAttempts);
103 | alert("Ship could not be placed. If there isn't room, try placing a smaller ship.");
104 | return;
105 | }
106 | }
107 | console.log('Determined that this ship will fit on the board.');
108 | // save ship's coordinates to ship object and board object
109 | ship = testShip;
110 | // place ship on board object, represented by a 1 for each of the ship's coordinate locations
111 | for (i = 0; i < ship.coords.length; i++) {
112 | var x = ship.coords[i][0];
113 | var y = ship.coords[i][1];
114 | this.board[x][y] = 1;
115 | console.log('Placed ship square ' + i + ' at ' + x + ', ' + y)
116 | }
117 | console.log('Ship of size ' + ship.coords.length + ' successfully placed on the board!')
118 | };
119 |
120 | this.displayShips = function() {
121 | for (i = 0; i < this.ships.length; i++) {
122 | for (j = 0; j < this.ships[i].coords.length; j++) {
123 | var x = this.ships[i].coords[j][0];
124 | var y = this.ships[i].coords[j][1];
125 |
126 | if (this.board[x][y] == 2) {
127 | document.getElementById('s'+x+'-'+y).style.background = 'red';
128 | } else if (this.board[x][y] == 3) {
129 | document.getElementById('s'+x+'-'+y).style.background = '#bbb';
130 | } else {
131 | document.getElementById('s'+x+'-'+y).style.background = this.ships[i].color;
132 | }
133 | }
134 | }
135 | };
136 |
137 | // return the ship object that occupies the given coordinate on the game board
138 | this.getShipByCoordinate = function(x, y) {
139 | for (i = 0; i < this.ships.length; i++) {
140 | for (j = 0; j < this.ships[i].coords.length; j++) {
141 | if (this.ships[i].coords[j][0] == x && this.ships[i].coords[j][1] == y) {
142 | // if the coordinate belongs to the current ship, return this ship
143 | return this.ships[i];
144 | } // otherwise, keep searching until finding a match
145 | }
146 | }
147 | // if all ships have been checked and no match, return false to indicate no ship has these coordinates
148 | return false;
149 | };
150 |
151 | // prevent user from clicking the board after game is over
152 | this.end = function() {
153 | gameBoardContainer.removeEventListener("click", onBoardClick, false);
154 | };
155 |
156 | // run this method when user clicks on the game board
157 | // boilerplate code via http://www.kirupa.com/html5/handling_events_for_many_elements.htm
158 | this.fireTorpedo = function (e) {
159 | var newMessage = '';
160 | // if item clicked (e.target) is not the parent element on which the event listener was set (e.currentTarget)
161 | if (e.target !== e.currentTarget) {
162 | // extract row and column # from the HTML element's id
163 | // TODO: rewrite this for dynamic board size with more than 10 columns
164 | var row = e.target.id.substring(1,2);
165 | var col = e.target.id.substring(3,4);
166 | console.log('Firing torpedo!');
167 |
168 | // if player clicks a square with no ship, change the color and change square's value
169 | if (this.board[row][col] == 0) {
170 | e.target.style.background = '#bbb';
171 | // set this square's value to 3 to indicate that they fired and missed
172 | this.board[row][col] = 3;
173 | this.missedCount++;
174 | console.log('Missed!');
175 | newMessage = ' Missed!';
176 |
177 | // if player clicks a square with a ship, change the color and change square's value
178 | } else if (this.board[row][col] == 1) {
179 | e.target.style.background = 'red';
180 | // set this square's value to 2 to indicate the ship has been hit
181 | this.board[row][col] = 2;
182 | // increment hitCount each time a ship is hit
183 | this.hitCount++;
184 |
185 | var currentShip = this.getShipByCoordinate(row, col);
186 | var isShipSunk = currentShip.takeDamage(row, col);
187 | if (isShipSunk) {
188 | newMessage = ' Battleship sunk!';
189 | this.shipsRemaining--;
190 | } else {
191 | newMessage = ' You hit a ship!';
192 | }
193 | console.log('Hit! Ships remaining: ' + this.shipsRemaining);
194 | console.log('isShipSunk: ' + isShipSunk);
195 | console.log('currentShip.damage: ' + currentShip.damage);
196 | if (this.shipsRemaining == 0) {
197 | newMessage = " All enemy battleships have been defeated! You win!";
198 | this.end(); // end the game, don't allow any more clicks on the board
199 | }
200 | // if player clicks a square that's been previously hit, let them know
201 | } else if (this.board[row][col] > 1) {
202 | newMessage = ' Stop wasting your torpedos! You already fired at this location.';
203 | }
204 | // update game score in HTML
205 | this.alerts.displayMessage('Hits: ' + this.hitCount + ' | Misses: ' + this.missedCount + ' | Ships remaining: ' + this.shipsRemaining + newMessage + '');
206 | }
207 | e.stopPropagation();
208 | };
209 | }
210 |
211 | // constructor for the Ship class
212 | function Ship(size) {
213 | this.damage = 0;
214 | this.coords = [];
215 | // pick a random color for each ship. code via http://stackoverflow.com/a/5092872
216 | this.color = "#000000".replace(/0/g,function(){return (~~(Math.random()*16)).toString(16);});
217 |
218 | // 2d coords array will contain [x,y] coordinate pairs for each square occupied (# of squares determined by size)
219 | for (i = 0; i < size; i++) {
220 | this.coords.push([]);
221 | }
222 |
223 | // update ship's damage count and return true if ship has been sunk
224 | this.takeDamage = function (x, y) {
225 | for (i = 0; i < this.coords.length; i++) {
226 | if (this.coords[i][0] == x && this.coords[i][1] == y) {
227 | this.damage++;
228 | console.log('Ship took damage at ' + x + ', ' + y + '. Damage: ' + this.damage);
229 | }
230 | }
231 | if (this.damage == this.coords.length) {
232 | return true; // ship has been sunk!
233 | } else {
234 | return false;
235 | }
236 | };
237 | }
238 |
239 | // global game variable, which will be modified by other functions
240 | var game;
241 |
242 | // function to use in addEventListener -- it needed a name so I can later use removeEventHandler
243 | // *** commented out for now, while I test the new ship placement algorithm
244 | //var onBoardClick = function (e) {
245 | // return game.fireTorpedo(e);
246 | //};
247 |
248 | function setupGame(e) {
249 | // get game board container element
250 | var gameBoardContainer = document.getElementById('gameboard');
251 | // clear the game board (for when resetting)
252 | gameBoardContainer.innerHTML = '';
253 | // create and setup the game board
254 | game = new BattleshipGame(10, 10, gameBoardContainer, document.getElementById('message'));
255 | console.log('Set up new game with a ' + game.board.length + ' by ' + game.board[0].length + ' board.');
256 | console.log(game);
257 |
258 | // create and randomly place some ships
259 | game.createShip(2);
260 | game.createShip(3);
261 | game.createShip(3);
262 | game.createShip(4);
263 | game.createShip(5);
264 |
265 | // for testing purposes, display all the ships on the board
266 | game.displayShips();
267 |
268 | // update the start button text to say restart
269 | // *** don't need this for testing:
270 | // e.target.innerHTML = 'Restart Game';
271 | // game.alerts.displayMessage('Click the board to fire!');
272 | // set event listener for all elements in gameboard, run fireTorpedo function when square is clicked
273 | // gameBoardContainer.addEventListener("click", onBoardClick, false);
274 | }
275 |
276 | // reset board visualization so animation can be run again
277 | function resetAnimation() {
278 | for (i = 0; i < game.board.length; i++) {
279 | for (j = 0; j < game.board[0].length; j++) {
280 | // revert to default border defined in style.css
281 | document.getElementById('s'+i+'-'+j).style.border = '';
282 | }
283 | }
284 | }
285 |
286 | function startAnimation() {
287 | resetAnimation();
288 | var speed = parseInt(document.getElementById('speed').value);
289 | var shipSize = 5; // test placement of a ship of size 3 **TODO: user input for this
290 | var consecutiveEmptySquares = [];
291 | var possibleShipLocations = [];
292 | testPlacementAlgorithm(0, 0, speed, shipSize, consecutiveEmptySquares, possibleShipLocations);
293 | console.log('Starting placement algorithm. Ship size:' + shipSize + '. Animation speed: ' + speed + ' milliseconds.');
294 | }
295 |
296 | // run and visualize algorithm for placing ships on the board
297 | function testPlacementAlgorithm(x, y, speed, shipSize, consecutiveEmptySquares, possibleShipLocations)
298 | {
299 | var message; // use this to hold info to display in HTML
300 | var currentSquare = document.getElementById('s' + x + '-' + y);
301 | // recursively check each square, rows then columns
302 | if (x < game.board.length) {
303 | if (y < game.board[0].length) {
304 | currentSquare.style.border = '2px dotted red';
305 | message = 'x = ' + x + ', y = ' + y;
306 | // if this square is empty, save its coordinates to consecutiveEmptySquares array
307 | if (game.board[x][y] == 0) {
308 | consecutiveEmptySquares.push([x, y]);
309 | message += ' | Empty square! Consecutive empty squares: ' + consecutiveEmptySquares.length;
310 |
311 | // if we have [shipSize] number of consecutive empty squares, add to possibleShipLocations array, reset consecutiveEmptySquares
312 | if (consecutiveEmptySquares.length == shipSize) {
313 | possibleShipLocations.push(consecutiveEmptySquares);
314 | message += "Number of possible locations for this ship: " + possibleShipLocations.length;
315 |
316 | // recolor squares where ship can be placed:
317 | for (i = 0; i < consecutiveEmptySquares.length; i++) {
318 | document.getElementById('s' + consecutiveEmptySquares[i][0] + '-' + consecutiveEmptySquares[i][1]).style.border = '2px solid orange';
319 | }
320 |
321 | // reset to check for next possible ship placements
322 | consecutiveEmptySquares = [];
323 | }
324 |
325 | } else {
326 | // if square is not empty, reset consecutiveEmptySquares
327 | consecutiveEmptySquares = [];
328 | message += " | There's a ship here!";
329 | }
330 |
331 | console.log('consecutiveEmptySquares: ');
332 | console.log(consecutiveEmptySquares);
333 | console.log('possibleShipLocations:');
334 | console.log(possibleShipLocations);
335 | game.alerts.displayMessage(message);
336 | setTimeout(function(){return testPlacementAlgorithm(x, y+1, speed, shipSize, consecutiveEmptySquares, possibleShipLocations);}, speed);
337 | } else {
338 | // at the end of each row, reset consecutiveEmptySquares (because ships can't span multiple rows!)
339 | consecutiveEmptySquares = [];
340 | setTimeout(function(){return testPlacementAlgorithm(x+1, 0, speed, shipSize, consecutiveEmptySquares, possibleShipLocations);}, speed);
341 | }
342 | }
343 | }
344 |
345 |
346 | // set event listeners so clicking buttons will run functions:
347 | // *** don't need this during testing:
348 | // document.getElementById('startbtn').addEventListener('click', setupGame);
349 | // document.getElementById('displayships').addEventListener('click', function () {game.displayShips();});
350 |
351 | // for testing algorithm:
352 | setupGame();
353 | document.getElementById('startbtn').addEventListener('click', startAnimation);
--------------------------------------------------------------------------------
/day20/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 30 Days of Web Dev - Day 19 - Animated Algorithm
5 |
6 |
7 |
8 |
9 |
16 |
Animated Algorithm
17 |
18 |
This problem has been bugging me: how can I determine with certainty if a ship of a given size can fit on the board? So far, I've learned how to use JavaScript's setTimeout function to visualize the algorithm I just started writing. Cool, huh? There's much so more to do, though!
19 |
20 |
21 |
22 |
23 |
24 |
25 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/day20/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 60px auto;
3 | width: 70%;
4 | max-width: 950px;
5 | }
6 | nav ul, footer ul {
7 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
8 | padding: 0px;
9 | list-style: none;
10 | font-weight: bold;
11 | }
12 | nav ul li, footer ul li {
13 | display: inline;
14 | margin-right: 20px;
15 | }
16 | a {
17 | text-decoration: none;
18 | color: #999;
19 | }
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 | h1 {
24 | font-size: 3em;
25 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
26 | }
27 | p, button, input, label {
28 | font-size: 1.5em;
29 | line-height: 1.4em;
30 | color: #333;
31 | margin-bottom:1em;
32 | }
33 | footer {
34 | clear:both;
35 | border-top: 1px solid #d5d5d5;
36 | font-size: .8em;
37 | }
38 |
39 | ul.posts {
40 | margin: 20px auto 40px;
41 | font-size: 1.5em;
42 | }
43 |
44 | ul.posts li {
45 | list-style: none;
46 | }
47 |
48 | #gameboard {
49 | position:relative;
50 | margin:0 auto 2em auto;
51 | }
52 |
53 | #gameboard div {
54 | position:absolute;
55 | -webkit-box-sizing: border-box; /* Safari 3.0 - 5.0, Chrome 1 - 9, Android 2.1 - 3.x */
56 | -moz-box-sizing: border-box; /* Firefox 1 - 28 */
57 | box-sizing: border-box; /* Safari 5.1+, Chrome 10+, Firefox 29+, Opera 7+, IE 8+, Android 4.0+, iOS any */
58 | background: #f6f8f9; /* Old browsers */
59 | border: 1px solid #ddd;
60 | }
61 |
62 | #btns, #message {
63 | text-align:center;
64 | }
65 |
66 | #message {
67 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
68 | color: #333;
69 | }
--------------------------------------------------------------------------------
/day21/battleship.js:
--------------------------------------------------------------------------------
1 | // this constructor function defines the BattleshipGame class, which runs the whole game
2 | function BattleshipGame(rows, cols, gameBoardContainer, alertsContainer) {
3 | this.board = [];
4 | this.ships = [];
5 | this.hitCount = 0;
6 | this.missedCount = 0;
7 | this.shipsRemaining = 0;
8 |
9 | // a helper object for sending alerts to the user
10 | function GameAlerts (alertsContainer) {
11 | this.displayMessage = function (newMessage) {
12 | alertsContainer.innerHTML = newMessage;
13 | };
14 | }
15 |
16 | this.alerts = new GameAlerts(alertsContainer);
17 |
18 | // set size of each grid square
19 | var squareSize = 50;
20 |
21 | // set up the empty game board and draw the UI
22 | for (i = 0; i < rows; i++) {
23 | // create empty 2d array
24 | this.board.push([]);
25 | for (j = 0; j < cols; j++) {
26 | // initialize game board 2d array with 0s
27 | this.board[i].push(0);
28 | // create a new HTML element for each grid square and make it the right size
29 | var square = document.createElement("div");
30 | gameBoardContainer.appendChild(square);
31 |
32 | // set each element's id to the format 's01'
33 | square.id = 's' + i + '-' + j;
34 |
35 | // use CSS absolute positioning to place each grid square on the page
36 | square.style.top = (i * squareSize) + 'px';
37 | square.style.left = (j * squareSize) + 'px';
38 |
39 | // set square size
40 | square.style.width = squareSize + 'px';
41 | square.style.height = squareSize + 'px';
42 |
43 | // set gameBoardContainer size based on square size, rows and cols
44 | gameBoardContainer.style.width = (squareSize * cols) + 'px';
45 | gameBoardContainer.style.height = (squareSize * rows) + 'px';
46 | }
47 | }
48 |
49 | // create and randomly place a ship (calls placeRandomShip function)
50 | this.createShip = function(size) {
51 | // quick lazy solution so the ship will definitely fit on the game board
52 | // TODO: generate ship sizes based on game board size or user inputs
53 | if (size > this.board.length) {
54 | size = this.board.length;
55 | }
56 | if (size > this.board[0].length) {
57 | size = this.board[0].length;
58 | }
59 | // create a new ship, add it to game.ships array
60 | var ship = new Ship(size);
61 | this.ships.push(ship);
62 | this.shipsRemaining++;
63 | // run function that generates ship's coordinates and displays it on the game board
64 | this.placeRandomShip(ship, 50);
65 | console.log('Number of ships on the board: ' + this.ships.length)
66 | console.log(this.ships);
67 | };
68 |
69 | // method for placing a ship in a random location
70 | this.placeRandomShip = function(ship, maxAttempts) {
71 | console.log('Attempting to place a ship of size ' + ship.coords.length);
72 | // pick random starting coordinates (limited by # of rows/cols)
73 | var x = Math.floor(Math.random() * this.board.length);
74 | var y = Math.floor(Math.random() * this.board[0].length);
75 | // pick random orientation, either 0 or 1 (0 = horizontal, 1 = vertical)
76 | var orientation = Math.floor(Math.random() * 2);
77 | console.log('Starting coords: ' + x + ', ' + y + '\nOrientation: ' + (orientation ? 'vertical' : 'horizontal'));
78 |
79 | // create a temporary ship in which to test generated coords; only place ship if there's room for it!
80 | var testShip = ship;
81 | for (i = 0; i < testShip.coords.length; i++) {
82 | // if current x, y coords exist on the board and the square is empty (contains a 0):
83 | if (typeof this.board[x] != 'undefined' && typeof this.board[x][y] != 'undefined' && this.board[x][y] == 0) {
84 | // save current coords to temporary ship
85 | testShip.coords[i] = [x, y];
86 | console.log('Trying testShip.coords[' + i + '] = ' + ship.coords[i]);
87 | if (orientation == 0) {
88 | y++; //increment y coord to generate a ship oriented horizontally
89 | } else {
90 | x++; // increment x coord for vertical ships
91 | }
92 | } else if (maxAttempts > 0) { // if this spot goes off the grid or a ship already exists there, try again until maxAttemps is reached:
93 | console.log("Ship can't be placed. maxAttempts = " + maxAttempts);
94 | // delete this ship!
95 | // decrement maxAttempts and call this function to try placing the ship again
96 | maxAttempts--;
97 | this.placeRandomShip(ship, maxAttempts);
98 | return;
99 | // everything after here in the placeRandomShip() function should only execute if there's room for the ship:
100 |
101 | } else { // if ship can't be placed and maxAttempts has been reached, tell the user to try again:
102 | console.log("Ship can't be placed. maxAttempts = " + maxAttempts);
103 | alert("Ship could not be placed. If there isn't room, try placing a smaller ship.");
104 | return;
105 | }
106 | }
107 | console.log('Determined that this ship will fit on the board.');
108 | // save ship's coordinates to ship object and board object
109 | ship = testShip;
110 | // place ship on board object, represented by a 1 for each of the ship's coordinate locations
111 | for (i = 0; i < ship.coords.length; i++) {
112 | var x = ship.coords[i][0];
113 | var y = ship.coords[i][1];
114 | this.board[x][y] = 1;
115 | console.log('Placed ship square ' + i + ' at ' + x + ', ' + y)
116 | }
117 | console.log('Ship of size ' + ship.coords.length + ' successfully placed on the board!')
118 | };
119 |
120 | this.displayShips = function() {
121 | for (i = 0; i < this.ships.length; i++) {
122 | for (j = 0; j < this.ships[i].coords.length; j++) {
123 | var x = this.ships[i].coords[j][0];
124 | var y = this.ships[i].coords[j][1];
125 |
126 | if (this.board[x][y] == 2) {
127 | document.getElementById('s'+x+'-'+y).style.background = 'red';
128 | } else if (this.board[x][y] == 3) {
129 | document.getElementById('s'+x+'-'+y).style.background = '#bbb';
130 | } else {
131 | document.getElementById('s'+x+'-'+y).style.background = this.ships[i].color;
132 | }
133 | }
134 | }
135 | };
136 |
137 | // return the ship object that occupies the given coordinate on the game board
138 | this.getShipByCoordinate = function(x, y) {
139 | for (i = 0; i < this.ships.length; i++) {
140 | for (j = 0; j < this.ships[i].coords.length; j++) {
141 | if (this.ships[i].coords[j][0] == x && this.ships[i].coords[j][1] == y) {
142 | // if the coordinate belongs to the current ship, return this ship
143 | return this.ships[i];
144 | } // otherwise, keep searching until finding a match
145 | }
146 | }
147 | // if all ships have been checked and no match, return false to indicate no ship has these coordinates
148 | return false;
149 | };
150 |
151 | // prevent user from clicking the board after game is over
152 | this.end = function() {
153 | gameBoardContainer.removeEventListener("click", onBoardClick, false);
154 | };
155 |
156 | // run this method when user clicks on the game board
157 | // boilerplate code via http://www.kirupa.com/html5/handling_events_for_many_elements.htm
158 | this.fireTorpedo = function (e) {
159 | var newMessage = '';
160 | // if item clicked (e.target) is not the parent element on which the event listener was set (e.currentTarget)
161 | if (e.target !== e.currentTarget) {
162 | // extract row and column # from the HTML element's id
163 | // TODO: rewrite this for dynamic board size with more than 10 columns
164 | var row = e.target.id.substring(1,2);
165 | var col = e.target.id.substring(3,4);
166 | console.log('Firing torpedo!');
167 |
168 | // if player clicks a square with no ship, change the color and change square's value
169 | if (this.board[row][col] == 0) {
170 | e.target.style.background = '#bbb';
171 | // set this square's value to 3 to indicate that they fired and missed
172 | this.board[row][col] = 3;
173 | this.missedCount++;
174 | console.log('Missed!');
175 | newMessage = ' Missed!';
176 |
177 | // if player clicks a square with a ship, change the color and change square's value
178 | } else if (this.board[row][col] == 1) {
179 | e.target.style.background = 'red';
180 | // set this square's value to 2 to indicate the ship has been hit
181 | this.board[row][col] = 2;
182 | // increment hitCount each time a ship is hit
183 | this.hitCount++;
184 |
185 | var currentShip = this.getShipByCoordinate(row, col);
186 | var isShipSunk = currentShip.takeDamage(row, col);
187 | if (isShipSunk) {
188 | newMessage = ' Battleship sunk!';
189 | this.shipsRemaining--;
190 | } else {
191 | newMessage = ' You hit a ship!';
192 | }
193 | console.log('Hit! Ships remaining: ' + this.shipsRemaining);
194 | console.log('isShipSunk: ' + isShipSunk);
195 | console.log('currentShip.damage: ' + currentShip.damage);
196 | if (this.shipsRemaining == 0) {
197 | newMessage = " All enemy battleships have been defeated! You win!";
198 | this.end(); // end the game, don't allow any more clicks on the board
199 | }
200 | // if player clicks a square that's been previously hit, let them know
201 | } else if (this.board[row][col] > 1) {
202 | newMessage = ' Stop wasting your torpedos! You already fired at this location.';
203 | }
204 | // update game score in HTML
205 | this.alerts.displayMessage('Hits: ' + this.hitCount + ' | Misses: ' + this.missedCount + ' | Ships remaining: ' + this.shipsRemaining + newMessage + '');
206 | }
207 | e.stopPropagation();
208 | };
209 |
210 | this.testNewAlgorithm = function () {
211 | if (possibleShipLocations.length == 0 && runHorizCheck == true && runVertCheck == true) {
212 | this.alerts.displayMessage('Nowhere left to place the ship!');
213 | return;
214 | } else if (possibleShipLocations.length == 0) {
215 | this.alerts.displayMessage('Try running the other algorithm first.');
216 | }
217 | console.log('called testNewAlgorithm');
218 | // pick a random entry from possibleShipLocations
219 | var randomIndex = Math.floor(Math.random() * possibleShipLocations.length);
220 | var chosenShipCoords = possibleShipLocations[randomIndex];
221 | console.log('randomIndex = ' + randomIndex);
222 | // create a new ship, add it to game.ships array
223 | var ship = new Ship(chosenShipCoords.length);
224 | this.ships.push(ship);
225 | this.shipsRemaining++;
226 |
227 | // place the ship on the board
228 | for (i = 0; i < chosenShipCoords.length; i++) {
229 | var x = chosenShipCoords[i][0];
230 | var y = chosenShipCoords[i][1];
231 | this.board[x][y] = 1;
232 | console.log('Placed ship square ' + i + ' at ' + x + ', ' + y)
233 | // display ship right away, for testing
234 | document.getElementById('s'+x+'-'+y).style.background = ship.color;
235 | }
236 |
237 | console.log('Number of ships on the board: ' + this.ships.length)
238 | console.log(this.ships);
239 |
240 | // remove chosen location from possibleShipLocations
241 | possibleShipLocations.splice(randomIndex, 1);
242 | console.log('possibleShipLocations:');
243 | console.log(possibleShipLocations);
244 |
245 | // update count in HTML
246 | document.getElementById('message2').innerHTML = "Possible remaining locations for this ship: " + possibleShipLocations.length + '';
247 | };
248 | }
249 |
250 | // constructor for the Ship class
251 | function Ship(size) {
252 | this.damage = 0;
253 | this.coords = [];
254 | // pick a random color for each ship. code via http://stackoverflow.com/a/5092872
255 | this.color = "#000000".replace(/0/g,function(){return (~~(Math.random()*16)).toString(16);});
256 |
257 | // 2d coords array will contain [x,y] coordinate pairs for each square occupied (# of squares determined by size)
258 | for (i = 0; i < size; i++) {
259 | this.coords.push([]);
260 | }
261 |
262 | // update ship's damage count and return true if ship has been sunk
263 | this.takeDamage = function (x, y) {
264 | for (i = 0; i < this.coords.length; i++) {
265 | if (this.coords[i][0] == x && this.coords[i][1] == y) {
266 | this.damage++;
267 | console.log('Ship took damage at ' + x + ', ' + y + '. Damage: ' + this.damage);
268 | }
269 | }
270 | if (this.damage == this.coords.length) {
271 | return true; // ship has been sunk!
272 | } else {
273 | return false;
274 | }
275 | };
276 | }
277 |
278 | // global game variable, which will be modified by other functions
279 | var game;
280 | // global variable to save list of possible ship locations, for use between functions while I test it out
281 | var possibleShipLocations = [];
282 | var runHorizCheck = false;
283 | var runVertCheck = false;
284 |
285 | // function to use in addEventListener -- it needed a name so I can later use removeEventHandler
286 | // *** commented out for now, while I test the new ship placement algorithm
287 | //var onBoardClick = function (e) {
288 | // return game.fireTorpedo(e);
289 | //};
290 |
291 | function setupGame(e) {
292 | // get game board container element
293 | var gameBoardContainer = document.getElementById('gameboard');
294 | // clear the game board (for when resetting)
295 | gameBoardContainer.innerHTML = '';
296 | // create and setup the game board
297 | game = new BattleshipGame(10, 10, gameBoardContainer, document.getElementById('message'));
298 | console.log('Set up new game with a ' + game.board.length + ' by ' + game.board[0].length + ' board.');
299 | console.log(game);
300 |
301 | // create and randomly place some ships
302 | game.createShip(2);
303 | game.createShip(3);
304 | game.createShip(3);
305 | game.createShip(4);
306 | game.createShip(5);
307 | // extra ships just for fun!
308 | game.createShip(2);
309 | game.createShip(2);
310 | game.createShip(2);
311 | game.createShip(3);
312 | game.createShip(3);
313 | game.createShip(4);
314 | game.createShip(5);
315 |
316 | // for testing purposes, display all the ships on the board
317 | game.displayShips();
318 |
319 | // update the start button text to say restart
320 | // *** don't need this for testing:
321 | // e.target.innerHTML = 'Restart Game';
322 | // game.alerts.displayMessage('Click the board to fire!');
323 | // set event listener for all elements in gameboard, run fireTorpedo function when square is clicked
324 | // gameBoardContainer.addEventListener("click", onBoardClick, false);
325 | }
326 |
327 | // reset board visualization so animation can be run again
328 | function resetAnimation() {
329 | for (i = 0; i < game.board.length; i++) {
330 | for (j = 0; j < game.board[0].length; j++) {
331 | // revert to default border defined in style.css
332 | document.getElementById('s'+i+'-'+j).style.border = '';
333 | }
334 | }
335 | }
336 |
337 | function startAnimationHorizontal() {
338 | resetAnimation();
339 | var speed = parseInt(document.getElementById('speed').value);
340 | var shipSize = parseInt(document.getElementById('size').value);
341 | var consecutiveEmptySquares = [];
342 | testPlacementAlgorithmHorizontal(0, 0, speed, shipSize, consecutiveEmptySquares, possibleShipLocations);
343 | console.log('Starting horizontal placement algorithm. Ship size:' + shipSize + '. Animation speed: ' + speed + ' milliseconds.');
344 | }
345 |
346 | function startAnimationVertical() {
347 | resetAnimation();
348 | var speed = parseInt(document.getElementById('speed').value);
349 | var shipSize = parseInt(document.getElementById('size').value);
350 | var consecutiveEmptySquares = [];
351 | testPlacementAlgorithmVertical(0, 0, speed, shipSize, consecutiveEmptySquares, possibleShipLocations);
352 | console.log('Starting vertical placement algorithm. Ship size:' + shipSize + '. Animation speed: ' + speed + ' milliseconds.');
353 | }
354 |
355 | // run and visualize algorithm for placing ships on the board horizontally
356 | function testPlacementAlgorithmHorizontal(x, y, speed, shipSize, consecutiveEmptySquares, possibleShipLocations)
357 | {
358 | var message; // use this to hold info to display in HTML
359 | var currentSquare = document.getElementById('s' + x + '-' + y);
360 | // recursively check each square, rows then columns
361 | if (x < game.board.length) {
362 | if (y < game.board[0].length) {
363 | currentSquare.style.border = '2px dotted #333';
364 | message = 'x = ' + x + ', y = ' + y;
365 | // if this square is empty, save its coordinates to consecutiveEmptySquares array
366 | if (game.board[x][y] == 0) {
367 | consecutiveEmptySquares.push([x, y]);
368 | message += ' | Empty square! Consecutive empty squares: ' + consecutiveEmptySquares.length;
369 |
370 | // if we have [shipSize] number of consecutive empty squares, add to possibleShipLocations array, reset consecutiveEmptySquares
371 | if (consecutiveEmptySquares.length == shipSize) {
372 | possibleShipLocations.push(consecutiveEmptySquares);
373 | document.getElementById('message2').innerHTML = "Possible remaining locations for this ship: " + possibleShipLocations.length + '';
374 |
375 | // recolor squares where ship can be placed:
376 | for (i = 0; i < consecutiveEmptySquares.length; i++) {
377 | document.getElementById('s' + consecutiveEmptySquares[i][0] + '-' + consecutiveEmptySquares[i][1]).style.border = '2px dotted red';
378 | }
379 |
380 | // reset to check for next possible ship placements
381 | consecutiveEmptySquares = [];
382 | }
383 |
384 | } else {
385 | // if square is not empty, reset consecutiveEmptySquares
386 | consecutiveEmptySquares = [];
387 | message += " | There's a ship here!";
388 | }
389 |
390 | console.log('consecutiveEmptySquares: ');
391 | console.log(consecutiveEmptySquares);
392 | console.log('possibleShipLocations:');
393 | console.log(possibleShipLocations);
394 | game.alerts.displayMessage(message);
395 | setTimeout(function(){return testPlacementAlgorithmHorizontal(x, y+1, speed, shipSize, consecutiveEmptySquares, possibleShipLocations);}, speed);
396 | } else {
397 | // at the end of each row, reset consecutiveEmptySquares (because ships can't span multiple rows!)
398 | consecutiveEmptySquares = [];
399 | runHorizCheck = true;
400 | setTimeout(function(){return testPlacementAlgorithmHorizontal(x+1, 0, speed, shipSize, consecutiveEmptySquares, possibleShipLocations);}, speed);
401 | }
402 | }
403 | }
404 |
405 | // run and visualize algorithm for placing ships on the board horizontally
406 | function testPlacementAlgorithmVertical(x, y, speed, shipSize, consecutiveEmptySquares, possibleShipLocations)
407 | {
408 | var message; // use this to hold info to display in HTML
409 | var currentSquare = document.getElementById('s' + x + '-' + y);
410 | // recursively check each square, rows then columns
411 | if (y < game.board[0].length) {
412 | if (x < game.board.length) {
413 | currentSquare.style.border = '2px dotted #333';
414 | message = 'x = ' + x + ', y = ' + y;
415 | // if this square is empty, save its coordinates to consecutiveEmptySquares array
416 | if (game.board[x][y] == 0) {
417 | consecutiveEmptySquares.push([x, y]);
418 | message += ' | Empty square! Consecutive empty squares: ' + consecutiveEmptySquares.length;
419 |
420 | // if we have [shipSize] number of consecutive empty squares, add to possibleShipLocations array, reset consecutiveEmptySquares
421 | if (consecutiveEmptySquares.length == shipSize) {
422 | possibleShipLocations.push(consecutiveEmptySquares);
423 | document.getElementById('message2').innerHTML = "Places found where the ship can fit: " + possibleShipLocations.length + '';
424 |
425 | // recolor squares where ship can be placed:
426 | for (i = 0; i < consecutiveEmptySquares.length; i++) {
427 | document.getElementById('s' + consecutiveEmptySquares[i][0] + '-' + consecutiveEmptySquares[i][1]).style.border = '2px dotted red';
428 | }
429 |
430 | // reset to check for next possible ship placements
431 | consecutiveEmptySquares = [];
432 | }
433 |
434 | } else {
435 | // if square is not empty, reset consecutiveEmptySquares
436 | consecutiveEmptySquares = [];
437 | message += " | There's a ship here!";
438 | }
439 |
440 | console.log('consecutiveEmptySquares: ');
441 | console.log(consecutiveEmptySquares);
442 | console.log('possibleShipLocations:');
443 | console.log(possibleShipLocations);
444 | game.alerts.displayMessage(message);
445 | setTimeout(function(){return testPlacementAlgorithmVertical(x+1, y, speed, shipSize, consecutiveEmptySquares, possibleShipLocations);}, speed);
446 | } else {
447 | // at the end of each row, reset consecutiveEmptySquares (because ships can't span multiple rows!)
448 | consecutiveEmptySquares = [];
449 | runVertCheck = true;
450 | setTimeout(function(){return testPlacementAlgorithmVertical(0, y+1, speed, shipSize, consecutiveEmptySquares, possibleShipLocations);}, speed);
451 | }
452 | }
453 | }
454 |
455 | // set event listeners so clicking buttons will run functions:
456 | // *** don't need this during testing:
457 | // document.getElementById('startbtn').addEventListener('click', setupGame);
458 | // document.getElementById('displayships').addEventListener('click', function () {game.displayShips();});
459 |
460 | // for testing algorithm:
461 | setupGame();
462 | document.getElementById('checkhoriz').addEventListener('click', startAnimationHorizontal);
463 | document.getElementById('checkvert').addEventListener('click', startAnimationVertical);
464 | document.getElementById('placeship').addEventListener('click', function() {return game.testNewAlgorithm();});
--------------------------------------------------------------------------------
/day21/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 30 Days of Web Dev - Day 21 - More Algorithmic Adventures
5 |
6 |
7 |
8 |
9 |
16 |
More Algorithmic Adventures
17 |
18 |
I think I finished my algorithm that tests whether a ship can fit on the board! There's still a bug, though. Can you spot it? (This is what I get for writing code while half-asleep.)
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/day21/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 60px auto;
3 | width: 70%;
4 | max-width: 950px;
5 | }
6 | nav ul, footer ul {
7 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
8 | padding: 0px;
9 | list-style: none;
10 | font-weight: bold;
11 | }
12 | nav ul li, footer ul li {
13 | display: inline;
14 | margin-right: 20px;
15 | }
16 | a {
17 | text-decoration: none;
18 | color: #999;
19 | }
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 | h1 {
24 | font-size: 3em;
25 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
26 | }
27 | p, button, input, label {
28 | font-size: 1.5em;
29 | line-height: 1.4em;
30 | color: #333;
31 | margin-bottom:1em;
32 | }
33 | footer {
34 | clear:both;
35 | border-top: 1px solid #d5d5d5;
36 | font-size: .8em;
37 | }
38 |
39 | ul.posts {
40 | margin: 20px auto 40px;
41 | font-size: 1.5em;
42 | }
43 |
44 | ul.posts li {
45 | list-style: none;
46 | }
47 |
48 | #gameboard {
49 | position:relative;
50 | margin:0 auto 2em auto;
51 | }
52 |
53 | #gameboard div {
54 | position:absolute;
55 | -webkit-box-sizing: border-box; /* Safari 3.0 - 5.0, Chrome 1 - 9, Android 2.1 - 3.x */
56 | -moz-box-sizing: border-box; /* Firefox 1 - 28 */
57 | box-sizing: border-box; /* Safari 5.1+, Chrome 10+, Firefox 29+, Opera 7+, IE 8+, Android 4.0+, iOS any */
58 | background: #f6f8f9; /* Old browsers */
59 | border: 1px solid #ddd;
60 | }
61 |
62 | #btns, .message{
63 | text-align:center;
64 | }
65 |
66 | .message {
67 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
68 | color: #333;
69 | }
--------------------------------------------------------------------------------
/day23/battleship.js:
--------------------------------------------------------------------------------
1 | // this constructor function defines the BattleshipGame class, which runs the whole game
2 | function BattleshipGame(rows, cols, gameBoardContainer, messageContainer1, messageContainer2) {
3 | this.board = [];
4 | this.ships = [];
5 | this.hitCount = 0;
6 | this.missedCount = 0;
7 | this.shipsRemaining = 0;
8 |
9 | // a helper object for sending alerts to the user
10 | function GameAlerts (messageContainer) {
11 | this.displayMessage = function (newMessage) {
12 | messageContainer.innerHTML = newMessage;
13 | };
14 | }
15 |
16 | this.msg1 = new GameAlerts(messageContainer1);
17 | this.msg2 = new GameAlerts(messageContainer2);
18 |
19 | // set size of each grid square
20 | var squareSize = 50;
21 |
22 | // set up the empty game board and draw the UI
23 | for (i = 0; i < rows; i++) {
24 | // create empty 2d array
25 | this.board.push([]);
26 | for (j = 0; j < cols; j++) {
27 | // initialize game board 2d array with 0s
28 | this.board[i].push(0);
29 | // create a new HTML element for each grid square and make it the right size
30 | var square = document.createElement("div");
31 | gameBoardContainer.appendChild(square);
32 |
33 | // set each element's id to the format 's01'
34 | square.id = 's' + i + '-' + j;
35 |
36 | // use CSS absolute positioning to place each grid square on the page
37 | square.style.top = (i * squareSize) + 'px';
38 | square.style.left = (j * squareSize) + 'px';
39 |
40 | // set square size
41 | square.style.width = squareSize + 'px';
42 | square.style.height = squareSize + 'px';
43 |
44 | // set gameBoardContainer size based on square size, rows and cols
45 | gameBoardContainer.style.width = (squareSize * cols) + 'px';
46 | gameBoardContainer.style.height = (squareSize * rows) + 'px';
47 | }
48 | }
49 |
50 | // create and randomly place a ship (calls placeRandomShip function)
51 | this.createShip = function(coords) {
52 | // create a new ship, add it to game.ships array
53 | console.log('Creating new ship with coordinates: ');
54 | console.log(coords);
55 | var ship = new Ship(coords);
56 | this.ships.push(ship);
57 | this.shipsRemaining++;
58 | console.log('Number of ships on the board: ' + this.ships.length)
59 | console.log(this.ships);
60 | return ship;
61 | };
62 |
63 | // method for placing a ship in a random location
64 | this.placeRandomShip = function(size) {
65 | console.log('Attempting to place a ship of size ' + size);
66 | var possibleShipLocations = [];
67 |
68 | function traverseBoard(game, orientation) {
69 | // note: assuming a square board!!! otherwise this will need to be done differently, with this.board[0].length...
70 | var msg = '';
71 | var consecutiveEmptySquares = [];
72 | for (i = 0; i < game.board.length; i++) {
73 | for (j = 0; j < game.board.length; j++) {
74 | var x, y;
75 | if (orientation == 0) {
76 | x = i, y = j;
77 | } else {
78 | y = i, x = j;
79 | }
80 | // if current square is empty, add to consecutiveEmptySquares
81 | if (game.board[x][y] == 0) {
82 | console.log(x + ',' + y + ' == 0');
83 | consecutiveEmptySquares.push([x,y]);
84 | } else {
85 | console.log(x + ',' + y + ' == 1 !!!!');
86 | consecutiveEmptySquares = []; // reset each time a ship is encountered
87 | }
88 | // if there are enough consecutiveEmptySquares to fit the ship, then add to possibleShipLocations and reset consecutiveEmptySquares
89 | if (consecutiveEmptySquares.length == size) {
90 | console.log('Added to possibleShipLocations:');
91 | console.log(consecutiveEmptySquares);
92 | possibleShipLocations.push(consecutiveEmptySquares);
93 | consecutiveEmptySquares = [];
94 | }
95 | }
96 | consecutiveEmptySquares = []; // reset for each row/column so ships don't wrap around the board
97 | }
98 | }
99 |
100 | console.log('possibleShipLocations: ');
101 | console.log(possibleShipLocations);
102 |
103 | // pick random orientation, either 0 or 1 (0 = horizontal, 1 = vertical)
104 | var orientation = Math.floor(Math.random() * 2);
105 | console.log('Orientation: ' + (orientation ? 'vertical' : 'horizontal'));
106 |
107 | // check the board by row or by column for possible ship locations:
108 | traverseBoard(this, orientation);
109 |
110 | // if no possibleShipLocations, check again with opposite orientation
111 | if (possibleShipLocations.length == 0) {
112 | msg = 'No ' + (orientation ? 'vertical' : 'horizontal') + ' places found for this ship. Checking for ' + (!orientation ? 'vertical' : 'horizontal') + ' places.';
113 | console.log(msg);
114 | game.msg1.displayMessage(msg);
115 | traverseBoard(this, !orientation);
116 |
117 | // if still no possibleShipLocations, there's no room left for a ship of this size
118 | if (possibleShipLocations.length == 0) {
119 | console.log('A ship of size ' + size + " won't fit on the board!");
120 | game.msg1.displayMessage('A ship of size ' + size + " won't fit on the board!");
121 | return;
122 | } else {
123 | msg = 'Found ' + possibleShipLocations.length + ' ' + (!orientation ? 'vertical' : 'horizontal') + ' locations for size ' + size + ' ship.';
124 | console.log(msg);
125 | game.msg1.displayMessage(msg);
126 | }
127 | } else {
128 | msg = 'Found ' + possibleShipLocations.length + ' ' + (orientation ? 'vertical' : 'horizontal') + ' locations for size ' + size + ' ship.';
129 | console.log(msg);
130 | game.msg1.displayMessage(msg);
131 | }
132 |
133 | // pick a random entry from possibleShipLocations
134 | var randomIndex = Math.floor(Math.random() * possibleShipLocations.length);
135 | var chosenShipCoords = possibleShipLocations[randomIndex];
136 | console.log('randomIndex = ' + randomIndex);
137 |
138 | // create a new ship with the chosen coordinates
139 | var newShip = this.createShip(chosenShipCoords);
140 |
141 | // place ship on the board and
142 | for (i = 0; i < chosenShipCoords.length; i++) {
143 | var x = chosenShipCoords[i][0];
144 | var y = chosenShipCoords[i][1];
145 | this.board[x][y] = 1;
146 | console.log('Placed ship square ' + i + ' at ' + x + ', ' + y)
147 | }
148 |
149 | //game.msg1.displayMessage('Placed ship on the board.');
150 | };
151 |
152 | this.displayShips = function() {
153 | for (i = 0; i < this.ships.length; i++) {
154 | for (j = 0; j < this.ships[i].coords.length; j++) {
155 | var x = this.ships[i].coords[j][0];
156 | var y = this.ships[i].coords[j][1];
157 |
158 | if (this.board[x][y] == 2) {
159 | document.getElementById('s'+x+'-'+y).style.background = 'red';
160 | } else if (this.board[x][y] == 3) {
161 | document.getElementById('s'+x+'-'+y).style.background = '#bbb';
162 | } else {
163 | document.getElementById('s'+x+'-'+y).style.background = this.ships[i].color;
164 | }
165 | }
166 | }
167 | };
168 |
169 | // return the ship object that occupies the given coordinate on the game board
170 | this.getShipByCoordinate = function(x, y) {
171 | for (i = 0; i < this.ships.length; i++) {
172 | for (j = 0; j < this.ships[i].coords.length; j++) {
173 | if (this.ships[i].coords[j][0] == x && this.ships[i].coords[j][1] == y) {
174 | // if the coordinate belongs to the current ship, return this ship
175 | return this.ships[i];
176 | } // otherwise, keep searching until finding a match
177 | }
178 | }
179 | // if all ships have been checked and no match, return false to indicate no ship has these coordinates
180 | return false;
181 | };
182 |
183 | // prevent user from clicking the board after game is over
184 | this.end = function() {
185 | gameBoardContainer.removeEventListener("click", onBoardClick, false);
186 | };
187 |
188 | // run this method when user clicks on the game board
189 | // boilerplate code via http://www.kirupa.com/html5/handling_events_for_many_elements.htm
190 | this.fireTorpedo = function (e) {
191 | var newMessage = '';
192 | // if item clicked (e.target) is not the parent element on which the event listener was set (e.currentTarget)
193 | if (e.target !== e.currentTarget) {
194 | // extract row and column # from the HTML element's id
195 | // TODO: rewrite this for dynamic board size with more than 10 columns
196 | var row = e.target.id.substring(1,2);
197 | var col = e.target.id.substring(3,4);
198 | console.log('Firing torpedo!');
199 |
200 | // if player clicks a square with no ship, change the color and change square's value
201 | if (this.board[row][col] == 0) {
202 | e.target.style.background = '#bbb';
203 | // set this square's value to 3 to indicate that they fired and missed
204 | this.board[row][col] = 3;
205 | this.missedCount++;
206 | console.log('Missed!');
207 | newMessage = ' Missed!';
208 |
209 | // if player clicks a square with a ship, change the color and change square's value
210 | } else if (this.board[row][col] == 1) {
211 | e.target.style.background = 'red';
212 | // set this square's value to 2 to indicate the ship has been hit
213 | this.board[row][col] = 2;
214 | // increment hitCount each time a ship is hit
215 | this.hitCount++;
216 |
217 | var currentShip = this.getShipByCoordinate(row, col);
218 | var isShipSunk = currentShip.takeDamage(row, col);
219 | if (isShipSunk) {
220 | newMessage = ' Battleship sunk!';
221 | this.shipsRemaining--;
222 | } else {
223 | newMessage = ' You hit a ship!';
224 | }
225 | console.log('Hit! Ships remaining: ' + this.shipsRemaining);
226 | console.log('isShipSunk: ' + isShipSunk);
227 | console.log('currentShip.damage: ' + currentShip.damage);
228 | if (this.shipsRemaining == 0) {
229 | newMessage = " All enemy battleships have been defeated! You win!";
230 | this.end(); // end the game, don't allow any more clicks on the board
231 | }
232 | // if player clicks a square that's been previously hit, let them know
233 | } else if (this.board[row][col] > 1) {
234 | newMessage = ' Stop wasting your torpedos! You already fired at this location.';
235 | }
236 | // update game score in HTML
237 | this.alerts.displayMessage('Hits: ' + this.hitCount + ' | Misses: ' + this.missedCount + ' | Ships remaining: ' + this.shipsRemaining + newMessage + '');
238 | }
239 | e.stopPropagation();
240 | };
241 | }
242 |
243 | // constructor for the Ship class
244 | function Ship(randomlyGeneratedCoords) {
245 | this.damage = 0;
246 | this.coords = [];
247 | // pick a random color for each ship. code via http://stackoverflow.com/a/5092872
248 | this.color = "#000000".replace(/0/g,function(){return (~~(Math.random()*16)).toString(16);});
249 |
250 | // 2d coords array will contain [x,y] coordinate pairs for each square occupied (# of squares determined by size)
251 | for (i = 0; i < randomlyGeneratedCoords.length; i++) {
252 | this.coords.push(randomlyGeneratedCoords[i]);
253 | }
254 |
255 | // update ship's damage count and return true if ship has been sunk
256 | this.takeDamage = function (x, y) {
257 | for (i = 0; i < this.coords.length; i++) {
258 | if (this.coords[i][0] == x && this.coords[i][1] == y) {
259 | this.damage++;
260 | console.log('Ship took damage at ' + x + ', ' + y + '. Damage: ' + this.damage);
261 | }
262 | }
263 | if (this.damage == this.coords.length) {
264 | return true; // ship has been sunk!
265 | } else {
266 | return false;
267 | }
268 | };
269 | }
270 |
271 |
272 | // global game variable, which will be modified by other functions
273 | var game;
274 |
275 | // function to use in addEventListener -- it needed a name so I can later use removeEventHandler
276 | // *** commented out for now, while I test the new ship placement algorithm
277 | //var onBoardClick = function (e) {
278 | // return game.fireTorpedo(e);
279 | //};
280 |
281 | function setupGame(e) {
282 | // get game board container element
283 | var gameBoardContainer = document.getElementById('gameboard');
284 | // clear the game board (for when resetting)
285 | gameBoardContainer.innerHTML = '';
286 | // create and setup the game board
287 | game = new BattleshipGame(10, 10, gameBoardContainer, document.getElementById('message'), document.getElementById('message2'));
288 | console.log('Set up new game with a ' + game.board.length + ' by ' + game.board[0].length + ' board.');
289 | console.log(game);
290 |
291 | // create and randomly place some ships
292 | // call game.placeRandomShip(size) now instead of game.createShip(size)
293 |
294 | // for testing purposes, display all the ships on the board
295 | game.displayShips();
296 |
297 | // update the start button text to say restart
298 | // *** don't need this for testing:
299 | // e.target.innerHTML = 'Restart Game';
300 | // game.alerts.displayMessage('Click the board to fire!');
301 | // set event listener for all elements in gameboard, run fireTorpedo function when square is clicked
302 | // gameBoardContainer.addEventListener("click", onBoardClick, false);
303 | }
304 |
305 | function placeOneShip() {
306 | var size = parseInt(document.getElementById('size').value);
307 | game.placeRandomShip(size);
308 | game.displayShips();
309 | }
310 |
311 | function placeFiveStandardShips() {
312 | game.placeRandomShip(5);
313 | game.placeRandomShip(4);
314 | game.placeRandomShip(3);
315 | game.placeRandomShip(3);
316 | game.placeRandomShip(2);
317 | game.displayShips();
318 | game.msg1.displayMessage('Randomly placed: carrier (size 5), battleship (size 4), destroyer (size 3), submarine (size 3), and patrol boat (size 2) according to official Hasbro rules (PDF).');
319 | }
320 |
321 | function resetGame () {
322 | setupGame();
323 | game.msg1.displayMessage('Reset the game board.');
324 | }
325 |
326 | // set event listeners so clicking buttons will run functions:
327 | // *** don't need this during testing:
328 | // document.getElementById('startbtn').addEventListener('click', setupGame);
329 | // document.getElementById('displayships').addEventListener('click', function () {game.displayShips();});
330 |
331 | // for testing algorithm:
332 | setupGame();
333 | document.getElementById('placeship').addEventListener('click', placeOneShip);
334 | document.getElementById('hasbrosetup').addEventListener('click', placeFiveStandardShips);
335 | document.getElementById('reset').addEventListener('click', resetGame);
--------------------------------------------------------------------------------
/day23/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 30 Days of Web Dev - Day 23 - Final Algorithm
5 |
6 |
7 |
8 |
9 |
16 |
Final Algorithm
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
Now my game can randomly place ships on the board and know without a doubt if a ship of a given size can still fit on the board. Try it out!
Reading about all the different ways to create basic CSS layouts. Just trying out a couple of techniques.
19 |
20 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla malesuada tortor at dapibus pellentesque. Cras commodo suscipit vestibulum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras auctor tincidunt mauris. In eget justo ipsum. Suspendisse aliquam aliquet odio quis gravida. Mauris pretium erat et quam convallis, ac hendrerit purus semper.
21 |
22 |
Nunc id leo magna. Mauris vulputate diam dictum porta porttitor. Nulla sodales velit ac erat varius rutrum. Sed ac rhoncus neque. Praesent ullamcorper feugiat felis, fermentum auctor dui posuere id.
23 |
24 |
25 |
Apple pie sweet roll cotton candy tiramisu cotton candy chocolate cake jujubes toffee. Jelly beans jelly-o chocolate cake jelly danish gummies gummi bears toffee.
26 |
27 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/day24/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 60px auto;
3 | width: 70%;
4 | max-width: 950px;
5 | }
6 | nav ul, footer ul {
7 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
8 | padding: 0px;
9 | list-style: none;
10 | font-weight: bold;
11 | }
12 | nav ul li, footer ul li {
13 | display: inline;
14 | margin-right: 20px;
15 | }
16 | a {
17 | text-decoration: none;
18 | color: #999;
19 | }
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 | h1 {
24 | font-size: 3em;
25 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
26 | }
27 | p, button, input, label {
28 | font-size: 1.5em;
29 | line-height: 1.4em;
30 | color: #333;
31 | margin-bottom:1em;
32 | }
33 | input {
34 | width:4em;
35 | }
36 | footer {
37 | clear:both;
38 | border-top: 1px solid #d5d5d5;
39 | font-size: .8em;
40 | }
41 |
42 | ul.posts {
43 | margin: 20px auto 40px;
44 | font-size: 1.5em;
45 | }
46 |
47 | ul.posts li {
48 | list-style: none;
49 | }
50 |
51 | #main {
52 | width: 65%;
53 | min-width: 300px;
54 | float: left;
55 | }
56 |
57 | #sidebar {
58 | width:30%;
59 | float:right;
60 | min-width:200px;
61 | }
62 |
63 | @media (max-width: 890px){
64 | #sidebar {
65 | width:100%;
66 | }
67 | #main {
68 | width:100%;
69 | }
70 | }
--------------------------------------------------------------------------------
/day25/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Day 25 - Using Jekyll
4 | ---
5 |
6 | #Using Jekyll
7 |
8 | Today I learned how to set up a local installation of [Jekyll](http://jekyllrb.com/), the static site generator built into [GitHub Pages](https://pages.github.com/). It was not especially easily! Thankfully, a few resources helped:
9 |
10 | - [Run Jekyll on Windows](http://jekyll-windows.juthilo.com/) - a great guide by [@juthilo](https://twitter.com/juthilo)
11 | - [Using Jekyll with Pages](https://help.github.com/articles/using-jekyll-with-pages/) - from GitHub.com
12 | - [Some Things I Learned While Buildling a Site on GitHub Pages](http://ilovesymposia.com/2015/01/04/some-things-i-learned-while-building-a-site-on-github-pages/) - by [@jnuneziglesias](https://twitter.com/jnuneziglesias)
13 |
14 | After banging my head on my desk for a little while, I now have it working smoothly. Woohoo! I even learned how to set up [Pygments](http://pygments.org/), a syntax highlighter that makes code snippets look nice. For example, this is what I typed into the command line to generate the stylesheet for syntax highlighting:
15 |
16 | {% highlight console %}
17 | $ cd GitHub-Pages-Folder
18 | $ pygmentize -S default -f html > pygments.css
19 | {% endhighlight %}
20 |
21 | And this page was written in [Markdown](https://help.github.com/articles/markdown-basics/) instead of HTML! [See the code here.](https://github.com/LearningNerd/30DaysOfWebDev/tree/gh-pages/day25) So I may not have been very creative today, but it does feel good to finally be more comfortable with using the command line and getting a sense for how all these tools work together.
--------------------------------------------------------------------------------
/day26/24px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LearningNerd/30DaysOfWebDev/3dda885b569a09ab0dc84958699864b4f6511d3c/day26/24px.png
--------------------------------------------------------------------------------
/day26/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 30 Days of Web Dev - Day 26 - Vertical Rhythm
5 |
6 |
7 |
8 |
9 |
Vertical Rhythm
10 |
11 |
Today I thought I'd practice creating a simple web page and stylesheet from scratch, and then I stumbled upon this idea of vertical rhythm for web typography so I thought I'd try that out too!
12 |
13 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus non tincidunt est, quis hendrerit nunc. Suspendisse vitae lorem sed est congue posuere. In auctor lorem a sapien ullamcorper pulvinar. Aliquam sodales eros tortor, in eleifend massa venenatis a. Pellentesque tincidunt volutpat felis eu convallis. Duis vehicula sapien odio, et facilisis tellus pulvinar eget. Fusce quam lacus, facilisis quis volutpat sed, gravida ac justo. Sed mauris dolor, viverra ac odio non, elementum interdum urna. Aliquam erat volutpat. Aliquam eget dapibus nisi, vitae ultrices neque. In nec est sed velit volutpat efficitur eu et diam. Quisque pellentesque facilisis porttitor. Vestibulum aliquet interdum elit vel hendrerit.
14 |
15 |
Subheading Here
16 |
17 |
In condimentum hendrerit dignissim. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean suscipit massa ac dignissim dignissim. In blandit nec velit at pellentesque. Vivamus porttitor maximus ex et posuere. Nunc ullamcorper ex dui, ut fermentum sem mattis vitae. Vivamus feugiat, felis at suscipit bibendum, magna arcu facilisis nisl, ac molestie justo risus at ante. Nunc aliquam nec mi condimentum rhoncus. Etiam sed dolor sagittis neque viverra fermentum.
18 |
19 |
Phasellus eget nisl enim. Sed vitae felis vel felis ullamcorper finibus id non nisl. Pellentesque finibus maximus turpis, vel congue justo. Quisque elementum, justo vitae feugiat lacinia, augue erat vestibulum urna, vel tincidunt arcu quam eu nunc. In fermentum tortor dictum egestas bibendum. Mauris fringilla erat nisl, in rhoncus neque pellentesque tristique. Donec aliquam, nisi volutpat efficitur faucibus, lorem sapien dignissim turpis, vitae tincidunt nunc nibh non ligula. Ut nec orci in lectus semper viverra a at nibh. Nunc elementum ante vitae eros dignissim consectetur. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris molestie luctus justo, id lobortis felis auctor a. Praesent nec ornare mi. Integer congue nulla imperdiet ligula mollis auctor.
A long time ago, I made my own Android app based on the Pomodoro Technique -- the general idea is to do focused work for a chunk of time (usually 25 minutes), followed by a 5 minute break. So today I thought I'd try to make a little pomodoro timer with JavaScript! So far it's just a simple countdown timer.
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | 00:00
17 |
--------------------------------------------------------------------------------
/day27/timer.js:
--------------------------------------------------------------------------------
1 | // some global variables to use between the timer functions
2 | var intervalID, startTime, userSetMilliseconds, elapsedMilliseconds = 0, timerPaused = true, timerWorkMode = true;
3 |
4 | // set event listeners for each button
5 | document.getElementById('start').addEventListener('click', startTimer);
6 | document.getElementById('reset').addEventListener('click', resetTimer);
7 |
8 | function startTimer () {
9 | //
10 | document.getElementById('timer').style.color = '';
11 | // if timer is paused, start it!
12 | if (timerPaused) {
13 | // if timer is being started (not resumed), initialize w/ user input
14 | if (elapsedMilliseconds == 0) {
15 | // get user input, convert from minutes to milliseconds
16 | userSetMilliseconds = parseInt(document.getElementById('time').value) * 60000;
17 | console.log('ElapsedMilliseconds: ' + elapsedMilliseconds + '. userSetMilliseconds: ' + userSetMilliseconds);
18 | }
19 | console.log('ElapsedMilliseconds: ' + elapsedMilliseconds + '. userSetMilliseconds: ' + userSetMilliseconds);
20 | // get the current time
21 | startTime = Date.now();
22 | // start timer: run the updateTimer function every 500 milliseconds
23 | intervalID = setInterval(updateTimer,500);
24 | } else { // if timer was running and is now being paused:
25 | clearInterval(intervalID);
26 | userSetMilliseconds -= elapsedMilliseconds;
27 | console.log('ElapsedMilliseconds: ' + elapsedMilliseconds + '. userSetMilliseconds: ' + userSetMilliseconds);
28 | }
29 | // toggle timerPaused
30 | timerPaused = !timerPaused;
31 | updateTimerButtons();
32 | }
33 |
34 | function resetTimer() {
35 | clearInterval(intervalID);
36 | userSetMilliseconds = 0;
37 | elapsedMilliseconds = 0;
38 | timerPaused = true;
39 | updateTimerButtons();
40 | document.getElementById('timer').innerHTML = '00:00';
41 | document.getElementById('timer').style.color = '';
42 | }
43 |
44 | function startBreak () {
45 | document.getElementById('time').value = '5';
46 | updateTimerButtons();
47 | }
48 |
49 | function startWork () {
50 | document.getElementById('time').value = '25';
51 | updateTimerButtons();
52 | }
53 |
54 | function updateTimerButtons() {
55 | var timerModeString = timerWorkMode ? 'Work' : 'Break';
56 | document.getElementById('start').innerHTML = timerPaused ? 'Start ' + timerModeString + ' Timer' : 'Pause ' + timerModeString + ' Timer';
57 | document.getElementById('reset').innerHTML = timerPaused ? 'Reset ' + timerModeString + ' Timer' : 'Reset ' + timerModeString + ' Timer';
58 | }
59 |
60 | function updateTimer() {
61 | var timerString = '';
62 |
63 | elapsedMilliseconds = (Date.now() - startTime);
64 | var remainingMilliseconds = userSetMilliseconds - elapsedMilliseconds;
65 |
66 | // if the timer is up, stop the timer and show a message
67 | if (remainingMilliseconds <= 0) {
68 | resetTimer();
69 | // toggle mode from work to break or break to work
70 | timerWorkMode = !timerWorkMode;
71 | updateTimerButtons()
72 | var timerModeString = timerWorkMode ? 'Time to get back to work!' : 'Time to take a break!';
73 | document.getElementById('timer').innerHTML = timerModeString;
74 | document.getElementById('timer').style.color = 'red';
75 | document.getElementById('timer').style.fontSize = '70px';
76 | if (!timerWorkMode) { // if now beginning a break, then start work! otherwise, start break
77 | startBreak();
78 | } else {
79 | startWork();
80 | }
81 | return;
82 | }
83 |
84 | // get minutes and seconds remaining, rounding down with Math.floor
85 | var secondsRemaining = Math.floor(remainingMilliseconds / 1000)
86 | var minutesRemaining = Math.floor(secondsRemaining / 60);
87 | var secondsRemainder = Math.floor(secondsRemaining % 60);
88 |
89 | // format timer as mm:ss. examples: 00:00, 00:09, 00:57, 02:30, 15:07
90 | if (minutesRemaining < 10) {
91 | timerString += '0'
92 | }
93 | timerString += minutesRemaining + ':';
94 | if (secondsRemainder < 10) {
95 | timerString += '0';
96 | }
97 | timerString += secondsRemainder;
98 |
99 | // display the timer in the HTML
100 | document.getElementById('timer').innerHTML = timerString;
101 | }
--------------------------------------------------------------------------------
/day28/index.html:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Day 28 - Local Web Storage
4 | javascriptfile: timer
5 | ---
6 |
Local Web Storage
7 |
8 |
Today I learned how to use local web storage with HTML5 and JavaScript's beforeunload event to make my timer work even if you close and reopen your web browser!
9 |
A long time ago, I made my own Android app based on the Pomodoro Technique -- the general idea is to do focused work for a chunk of time (usually 25 minutes), followed by a 5 minute break. Now I'm building a simple version of it with JavaScript!
10 |
11 |
12 |
13 |
14 |
15 |
Finished pomodoros: 0
16 |
17 |
18 |
19 | 00:00
20 |
--------------------------------------------------------------------------------
/day28/timer.js:
--------------------------------------------------------------------------------
1 | // some global variables to use between the timer functions
2 | var intervalID, startTime, userSetMilliseconds, userSetWorkDuration = '25', userSetBreakDuration = '5',
3 | elapsedMilliseconds = 0, timerPaused = true, timerWorkMode = true, timerOn = false,
4 | finishedPomodoros = 0;
5 |
6 | // set event listeners for each button
7 | document.getElementById('start').addEventListener('click', startTimer);
8 | document.getElementById('reset').addEventListener('click', resetTimer);
9 | document.getElementById('resethistory').addEventListener('click', resetHistory);
10 | window.addEventListener('beforeunload', saveState); // save timer state when page is closed
11 |
12 | // if there's stuff in local storage, resume previous state
13 | if(localStorage.getItem('intervalID')) {
14 | console.log('CALLING RESUMESTATE 1');
15 | resumeState();
16 | }
17 |
18 | console.log('ID: ' + intervalID);
19 | console.log('timerOn: ' + timerOn);
20 | console.log('timerWorkMode: ' + timerWorkMode);
21 | console.log('timerPaused: ' + timerPaused);
22 | console.log('Finished pomodoros: ' + finishedPomodoros);
23 | console.log('userSetMilliseconds: ' + userSetMilliseconds);
24 | console.log('elapsedMilliseconds: ' + elapsedMilliseconds);
25 |
26 | function startTimer () {
27 | console.log('in startTimer, timerOn: ' + timerOn);
28 | // reset to default font color (because font turns red when timer is up)
29 | document.getElementById('timer').style.color = '';
30 |
31 | // to save user's preference for work and break session duration
32 | if (timerWorkMode) {
33 | userSetWorkDuration = document.getElementById('time').value;
34 | } else {
35 | userSetBreakDuration = document.getElementById('time').value;
36 | }
37 | // if timer is paused, start it!
38 | if (timerPaused) {
39 | timerOn = true;
40 | timerPaused = false;
41 | // if timer is being started (not resumed), initialize w/ user input
42 | if (elapsedMilliseconds == 0) {
43 | // get user input, convert from minutes to milliseconds
44 | userSetMilliseconds = parseInt(document.getElementById('time').value) * 60000;
45 | console.log('ElapsedMilliseconds: ' + elapsedMilliseconds + '. userSetMilliseconds: ' + userSetMilliseconds);
46 | }
47 | console.log('ElapsedMilliseconds: ' + elapsedMilliseconds + '. userSetMilliseconds: ' + userSetMilliseconds);
48 | // get the current time
49 | startTime = Date.now();
50 | // start timer: run the updateTimer function every 500 milliseconds
51 | intervalID = setInterval(updateTimer,500);
52 | } else { // if timer was running and is now being paused:
53 | // toggle timerOn (to save status when reopening the web page)
54 | timerOn = false;
55 | timerPaused = true;
56 | clearInterval(intervalID);
57 | userSetMilliseconds -= elapsedMilliseconds;
58 | console.log('ElapsedMilliseconds: ' + elapsedMilliseconds + '. userSetMilliseconds: ' + userSetMilliseconds);
59 | }
60 | updateTimerButtons();
61 | }
62 |
63 | function resetTimer() {
64 | clearInterval(intervalID);
65 | userSetMilliseconds = 0;
66 | elapsedMilliseconds = 0;
67 | timerPaused = true;
68 | updateTimerButtons();
69 | document.getElementById('timer').innerHTML = '00:00';
70 | document.getElementById('timer').style.color = '';
71 | }
72 |
73 | function startBreak () {
74 | document.getElementById('time').value = userSetBreakDuration;
75 | updateTimerButtons();
76 | }
77 |
78 | function startWork () {
79 | document.getElementById('time').value = userSetWorkDuration;
80 | updateTimerButtons();
81 | }
82 |
83 | function updateTimerButtons() {
84 | var timerModeString = timerWorkMode ? 'Work' : 'Break';
85 | document.getElementById('start').innerHTML = timerPaused ? 'Start ' + timerModeString + ' Timer' : 'Pause ' + timerModeString + ' Timer';
86 | document.getElementById('reset').innerHTML = timerPaused ? 'Reset ' + timerModeString + ' Timer' : 'Reset ' + timerModeString + ' Timer';
87 | }
88 |
89 | function displayTimer(remainingMilliseconds) {
90 | var timerString = '';
91 | // get minutes and seconds remaining, rounding down with Math.floor
92 | var secondsRemaining = Math.floor(remainingMilliseconds / 1000)
93 | var minutesRemaining = Math.floor(secondsRemaining / 60);
94 | var secondsRemainder = Math.floor(secondsRemaining % 60);
95 |
96 | // format timer as mm:ss. examples: 00:00, 00:09, 00:57, 02:30, 15:07
97 | if (minutesRemaining < 10) {
98 | timerString += '0'
99 | }
100 | timerString += minutesRemaining + ':';
101 | if (secondsRemainder < 10) {
102 | timerString += '0';
103 | }
104 | timerString += secondsRemainder;
105 |
106 | // display the timer in the HTML
107 | document.getElementById('timer').innerHTML = timerString;
108 | }
109 |
110 | function updateTimer() {
111 | elapsedMilliseconds = (Date.now() - startTime);
112 | var remainingMilliseconds = userSetMilliseconds - elapsedMilliseconds;
113 |
114 | // if the timer is up, stop the timer and show a message
115 | if (remainingMilliseconds <= 0) {
116 | if (timerWorkMode) {
117 | finishedPomodoros++; // save # of pomodoros completed
118 | document.getElementById('pomodoros').innerHTML = finishedPomodoros;
119 | console.log('Completed a pomodoro! #: ' + finishedPomodoros);
120 | }
121 | resetTimer();
122 | // toggle mode from work to break or break to work
123 | timerWorkMode = !timerWorkMode;
124 | updateTimerButtons()
125 | var timerModeString = timerWorkMode ? 'Time to get back to work!' : 'Time to take a break!';
126 | document.getElementById('timer').innerHTML = timerModeString;
127 | document.getElementById('timer').style.color = 'red';
128 | document.getElementById('timer').style.fontSize = '70px';
129 | if (!timerWorkMode) { // if now beginning a break, then start work! otherwise, start break
130 | startBreak();
131 | } else {
132 | startWork();
133 | }
134 | return;
135 | }
136 |
137 | displayTimer(remainingMilliseconds);
138 | }
139 |
140 | // code via http://diveintohtml5.info/storage.html
141 | function supportsLocalStorage() {
142 | try {
143 | return 'localStorage' in window && window['localStorage'] !== null;
144 | } catch (e) {
145 | return false;
146 | }
147 | }
148 |
149 | function saveState() {
150 | if (!supportsLocalStorage()) { return false; }
151 | console.log('CALLLING SAVESTATE!');
152 | localStorage['intervalID'] = intervalID;
153 | localStorage['timerOn'] = timerOn;
154 | localStorage['timerPaused'] = timerPaused;
155 | localStorage['timerWorkMode'] = timerWorkMode;
156 | localStorage['userSetWorkDuration'] = userSetWorkDuration;
157 | localStorage['userSetBreakDuration'] = userSetBreakDuration;
158 | localStorage['userSetMilliseconds'] = userSetMilliseconds;
159 | localStorage['elapsedMilliseconds'] = elapsedMilliseconds;
160 | localStorage['finishedPomodoros'] = finishedPomodoros;
161 | return true;
162 | }
163 |
164 | function resumeState() {
165 | console.log('CALLING RESUMESTATE 2');
166 | intervalID = parseInt(localStorage['intervalID']);
167 | timerOn = (localStorage['timerOn'] == 'true');
168 | timerPaused = (localStorage['timerPaused'] == 'true');
169 | timerWorkMode = (localStorage['timerWorkMode'] == 'true'); // convert from string 'true'/'false' to actual boolean TRUE/FALSE
170 | userSetWorkDuration = localStorage['userSetWorkDuration'];
171 | userSetBreakDuration = localStorage['userSetBreakDuration'];
172 | userSetMilliseconds = parseInt(localStorage['userSetMilliseconds']);
173 | elapsedMilliseconds = parseInt(localStorage['elapsedMilliseconds']);
174 | finishedPomodoros = parseInt(localStorage['finishedPomodoros']);
175 |
176 | // show user's preference for work and break session duration
177 | if (timerWorkMode) {
178 | document.getElementById('time').value = userSetWorkDuration;
179 | } else {
180 | document.getElementById('time').value = userSetBreakDuration;
181 | }
182 |
183 | updateTimerButtons();
184 |
185 | userSetMilliseconds -= elapsedMilliseconds;
186 |
187 | displayTimer(userSetMilliseconds);
188 |
189 | console.log('timerOn right after resumeState: ' + timerOn);
190 | if (timerOn) {
191 | console.log('CALLING STARTTIMER');
192 | timerPaused = true; // then toggle it back on in startTimer() -- temporary workaround for messy code!
193 | startTimer();
194 | }
195 |
196 | document.getElementById('pomodoros').innerHTML = finishedPomodoros;
197 | }
198 |
199 | function resetHistory() {
200 | localStorage.clear();
201 | finishedPomodoros = 0;
202 |
203 | document.getElementById('pomodoros').innerHTML = finishedPomodoros;
204 | }
--------------------------------------------------------------------------------
/day29/ding.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LearningNerd/30DaysOfWebDev/3dda885b569a09ab0dc84958699864b4f6511d3c/day29/ding.wav
--------------------------------------------------------------------------------
/day29/index.html:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Day 29 - Better Pomodoro Timer
4 | javascriptfile: timer
5 | ---
6 |
Better Pomodoro Timer
7 |
8 |
After learning how to use local web storage yesterday, I fixed some of the bugs in my JavaScript timer and figured how to play a "ding!" sound when the timer is done.
9 |
A long time ago, I made my own Android app based on the Pomodoro Technique -- the general idea is to do focused work for a chunk of time (usually 25 minutes), followed by a 5 minute break. Now I'm building a simple version of it with JavaScript!
10 |
11 |
12 |
13 |
14 |
15 |
Finished pomodoros: 0
16 |
17 |
18 |
19 | 00:00
20 |
--------------------------------------------------------------------------------
/day29/timer.js:
--------------------------------------------------------------------------------
1 | // some global variables to use between the timer functions
2 | var intervalID, startTime, userSetMilliseconds, userSetWorkDuration = '25', userSetBreakDuration = '5',
3 | elapsedMilliseconds = 0, timerPaused = true, timerWorkMode = true, timerOn = false,
4 | finishedPomodoros = 0;
5 |
6 | // set event listeners for each button
7 | document.getElementById('start').addEventListener('click', startTimer);
8 | document.getElementById('reset').addEventListener('click', resetTimer);
9 | document.getElementById('resethistory').addEventListener('click', resetHistory);
10 | window.addEventListener('beforeunload', saveState); // save timer state when page is closed
11 |
12 | // if there's stuff in local storage, resume previous state
13 | if(localStorage.getItem('intervalID')) {
14 | resumeState();
15 | }
16 |
17 | console.log('ID: ' + intervalID);
18 | console.log('timerOn: ' + timerOn);
19 | console.log('timerWorkMode: ' + timerWorkMode);
20 | console.log('timerPaused: ' + timerPaused);
21 | console.log('Finished pomodoros: ' + finishedPomodoros);
22 | console.log('userSetMilliseconds: ' + userSetMilliseconds);
23 | console.log('elapsedMilliseconds: ' + elapsedMilliseconds);
24 |
25 | function startTimer () {
26 | console.log('in startTimer, timerOn: ' + timerOn);
27 | // reset to default font color (because font turns red when timer is up)
28 | document.getElementById('timer').style.color = '';
29 |
30 | // to save user's preference for work and break session duration
31 | if (timerWorkMode) {
32 | userSetWorkDuration = document.getElementById('time').value;
33 | } else {
34 | userSetBreakDuration = document.getElementById('time').value;
35 | }
36 | // if timer is paused, start it!
37 | if (timerPaused) {
38 | timerOn = true;
39 | timerPaused = false;
40 | // if timer is being started (not resumed), initialize w/ user input
41 | if (elapsedMilliseconds == 0) {
42 | // get user input, convert from minutes to milliseconds
43 | userSetMilliseconds = parseInt(document.getElementById('time').value) * 60000;
44 | console.log('ElapsedMilliseconds: ' + elapsedMilliseconds + '. userSetMilliseconds: ' + userSetMilliseconds);
45 | }
46 | console.log('ElapsedMilliseconds: ' + elapsedMilliseconds + '. userSetMilliseconds: ' + userSetMilliseconds);
47 | // get the current time
48 | startTime = Date.now();
49 | // start timer: run the updateTimer function every 500 milliseconds
50 | intervalID = setInterval(updateTimer,500);
51 | } else { // if timer was running and is now being paused:
52 | // toggle timerOn (to save status when reopening the web page)
53 | timerOn = false;
54 | timerPaused = true;
55 | clearInterval(intervalID);
56 | userSetMilliseconds -= elapsedMilliseconds;
57 | console.log('ElapsedMilliseconds: ' + elapsedMilliseconds + '. userSetMilliseconds: ' + userSetMilliseconds);
58 | }
59 | updateTimerButtons();
60 | }
61 |
62 | function resetTimer() {
63 | clearInterval(intervalID);
64 | userSetMilliseconds = 0;
65 | elapsedMilliseconds = 0;
66 | timerPaused = true;
67 | timerOn = false;
68 | updateTimerButtons();
69 | document.getElementById('timer').innerHTML = '00:00';
70 | document.getElementById('timer').style.color = '';
71 | }
72 |
73 | function startBreak () {
74 | document.getElementById('time').value = userSetBreakDuration;
75 | updateTimerButtons();
76 | }
77 |
78 | function startWork () {
79 | document.getElementById('time').value = userSetWorkDuration;
80 | updateTimerButtons();
81 | }
82 |
83 | function updateTimerButtons() {
84 | var timerModeString = timerWorkMode ? 'Work' : 'Break';
85 | document.getElementById('start').innerHTML = timerPaused ? 'Start ' + timerModeString + ' Timer' : 'Pause ' + timerModeString + ' Timer';
86 | document.getElementById('reset').innerHTML = timerPaused ? 'Reset ' + timerModeString + ' Timer' : 'Reset ' + timerModeString + ' Timer';
87 | }
88 |
89 | function displayTimer(remainingMilliseconds) {
90 | var timerString = '';
91 | // get minutes and seconds remaining, rounding down with Math.floor
92 | var secondsRemaining = Math.floor(remainingMilliseconds / 1000)
93 | var minutesRemaining = Math.floor(secondsRemaining / 60);
94 | var secondsRemainder = Math.floor(secondsRemaining % 60);
95 |
96 | // format timer as mm:ss. examples: 00:00, 00:09, 00:57, 02:30, 15:07
97 | if (minutesRemaining < 10) {
98 | timerString += '0'
99 | }
100 | timerString += minutesRemaining + ':';
101 | if (secondsRemainder < 10) {
102 | timerString += '0';
103 | }
104 | timerString += secondsRemainder;
105 |
106 | // display the timer in the HTML
107 | document.getElementById('timer').innerHTML = timerString;
108 | }
109 |
110 | function playTimerDoneSound() {
111 | var ding = new Audio('ding.wav');
112 | ding.play();
113 | }
114 |
115 | function updateTimer() {
116 | elapsedMilliseconds = (Date.now() - startTime);
117 | var remainingMilliseconds = userSetMilliseconds - elapsedMilliseconds;
118 |
119 | // if the timer is up, stop the timer and show a message
120 | if (remainingMilliseconds <= 0) {
121 | playTimerDoneSound();
122 | if (timerWorkMode) {
123 | finishedPomodoros++; // save # of pomodoros completed
124 | document.getElementById('pomodoros').innerHTML = finishedPomodoros;
125 | console.log('Completed a pomodoro! #: ' + finishedPomodoros);
126 | }
127 | resetTimer();
128 | // toggle mode from work to break or break to work
129 | timerWorkMode = !timerWorkMode;
130 | var timerModeString = timerWorkMode ? 'Time to get back to work!' : 'Time to take a break!';
131 | document.getElementById('timer').innerHTML = timerModeString;
132 | document.getElementById('timer').style.color = 'red';
133 | document.getElementById('timer').style.fontSize = '70px';
134 | if (!timerWorkMode) { // if now beginning a break, then start work! otherwise, start break
135 | startBreak();
136 | } else {
137 | startWork();
138 | }
139 | return;
140 | }
141 |
142 | displayTimer(remainingMilliseconds);
143 | }
144 |
145 | // code via http://diveintohtml5.info/storage.html
146 | function supportsLocalStorage() {
147 | try {
148 | return 'localStorage' in window && window['localStorage'] !== null;
149 | } catch (e) {
150 | return false;
151 | }
152 | }
153 |
154 | function saveState() {
155 | if (!supportsLocalStorage()) { return false; }
156 | console.log('CALLLING SAVESTATE!');
157 | if (!timerPaused) { // so timer will continue where it left off when window is reopened
158 | userSetMilliseconds -= elapsedMilliseconds;
159 | }
160 | localStorage['intervalID'] = intervalID;
161 | localStorage['timerOn'] = timerOn;
162 | localStorage['timerPaused'] = timerPaused;
163 | localStorage['timerWorkMode'] = timerWorkMode;
164 | localStorage['userSetWorkDuration'] = userSetWorkDuration;
165 | localStorage['userSetBreakDuration'] = userSetBreakDuration;
166 | localStorage['userSetMilliseconds'] = userSetMilliseconds;
167 | localStorage['elapsedMilliseconds'] = elapsedMilliseconds;
168 | localStorage['finishedPomodoros'] = finishedPomodoros;
169 | return true;
170 | }
171 |
172 | function resumeState() {
173 | console.log('CALLING RESUMESTATE!');
174 | intervalID = parseInt(localStorage['intervalID']);
175 | timerOn = (localStorage['timerOn'] == 'true');
176 | timerPaused = (localStorage['timerPaused'] == 'true');
177 | timerWorkMode = (localStorage['timerWorkMode'] == 'true'); // convert from string 'true'/'false' to actual boolean TRUE/FALSE
178 | userSetWorkDuration = localStorage['userSetWorkDuration'];
179 | userSetBreakDuration = localStorage['userSetBreakDuration'];
180 | userSetMilliseconds = parseInt(localStorage['userSetMilliseconds']);
181 | elapsedMilliseconds = parseInt(localStorage['elapsedMilliseconds']);
182 | finishedPomodoros = parseInt(localStorage['finishedPomodoros']);
183 |
184 | // show user's preference for work and break session duration
185 | if (timerWorkMode) {
186 | document.getElementById('time').value = userSetWorkDuration;
187 | } else {
188 | document.getElementById('time').value = userSetBreakDuration;
189 | }
190 |
191 |
192 | updateTimerButtons();
193 |
194 | displayTimer(userSetMilliseconds);
195 |
196 | console.log('timerOn right after resumeState: ' + timerOn);
197 | if (timerOn) {
198 | console.log('CALLING STARTTIMER');
199 | timerPaused = true; // then toggle it back on in startTimer() -- temporary workaround for messy code!
200 | startTimer();
201 | }
202 |
203 | document.getElementById('pomodoros').innerHTML = finishedPomodoros;
204 | }
205 |
206 | function resetHistory() {
207 | localStorage.clear();
208 | finishedPomodoros = 0;
209 |
210 | document.getElementById('pomodoros').innerHTML = finishedPomodoros;
211 | }
--------------------------------------------------------------------------------
/day3/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 30 Days of Web Dev - Day 3 - CSS Floats
5 |
6 |
7 |
8 |
9 |
16 |
Testing CSS Floats
17 |
18 |
left left left
19 |
right right right right
20 |
21 |
left left left left left left
22 |
23 | Bacon ipsum dolor amet rump bresaola short ribs drumstick kevin turkey pork chop jerky shoulder strip steak. Cow rump picanha leberkas pork belly fatback pastrami doner chuck kevin brisket drumstick pig. Picanha venison ball tip short ribs prosciutto, andouille bresaola fatback brisket. Pork loin cow short ribs brisket biltong beef. Tenderloin kielbasa shank, shankle pancetta cupim ground round bresaola bacon. Prosciutto jowl tongue filet mignon biltong jerky porchetta salami venison leberkas ham landjaeger rump.
24 | Doner pork chop drumstick, alcatra cupim pork belly meatloaf kevin tongue capicola beef ribs beef turducken sausage. Strip steak chicken tri-tip swine hamburger, kevin beef ribs kielbasa leberkas pork loin boudin pork chop shoulder turkey brisket. Swine landjaeger meatball doner, ribeye cow turkey ground round. Ball tip andouille shank jowl leberkas, cow meatloaf ham hock.Bacon ipsum dolor amet turkey t-bone sirloin tenderloin chuck chicken, pastrami picanha capicola pork loin sausage short ribs meatloaf venison. Flank kevin tail picanha venison. Jerky hamburger turkey beef sirloin short ribs tenderloin. Venison salami short loin ball tip corned beef, pork belly ham hock boudin kielbasa chicken biltong meatball filet mignon chuck.
25 |
26 |
27 |
28 | we heard you like floats, so we put a float in your float so you can float while you float!
29 |
left left left
30 |
right right right right
31 |
32 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/day3/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 60px auto;
3 | width: 70%;
4 | max-width: 950px;
5 | }
6 | nav ul, footer ul {
7 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
8 | padding: 0px;
9 | list-style: none;
10 | font-weight: bold;
11 | }
12 | nav ul li, footer ul li {
13 | display: inline;
14 | margin-right: 20px;
15 | }
16 | a {
17 | text-decoration: none;
18 | color: #999;
19 | }
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 | h1 {
24 | font-size: 3em;
25 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
26 | }
27 | p, button {
28 | font-size: 1.5em;
29 | line-height: 1.4em;
30 | color: #333;
31 | margin-bottom:1em;
32 | }
33 | footer {
34 | clear:both;
35 | border-top: 1px solid #d5d5d5;
36 | font-size: .8em;
37 | }
38 |
39 | ul.posts {
40 | margin: 20px auto 40px;
41 | font-size: 1.5em;
42 | }
43 |
44 | ul.posts li {
45 | list-style: none;
46 | }
47 | .left {
48 | float:left;
49 | }
50 | .right {
51 | float:right;
52 | }
53 | #testone {
54 | width:200px;
55 | height:100px;
56 | background:#99eecc;
57 | }
58 | #testtwo {
59 | width:200px;
60 | height:100px;
61 | background:#ee99cc;
62 | }
63 | #testthree {
64 | width:200px;
65 | height:50px;
66 | background:#cc99ee;
67 | }
68 | #testfour {
69 | background:#ccee99;
70 | }
71 | #testfive {
72 | width:500px;
73 | background:#ccc;
74 | }
75 | #testsix {
76 | }
77 | .clear {
78 | clear:both;
79 | margin-top:2em;
80 | }
--------------------------------------------------------------------------------
/day4/cupcakestrings.js:
--------------------------------------------------------------------------------
1 | function lowerCase() {
2 | // get the current value of the text inside the textarea with ID "cupcakeinput"
3 | var cupcakeInput = document.getElementById("cupcakeinput").value;
4 | document.getElementById("cupcakeinput").value = cupcakeInput.toLowerCase();
5 | }
6 |
7 | // run lowerCase function when button with ID "tolower" is pressed
8 | document.getElementById('tolower').addEventListener('click', lowerCase);
9 |
10 | function upperCase() {
11 | // get the current value of the text inside the textarea with ID "cupcakeinput"
12 | var cupcakeInput = document.getElementById("cupcakeinput").value;
13 | document.getElementById("cupcakeinput").value = cupcakeInput.toUpperCase();
14 | }
15 |
16 | // run upperCase function when button with ID "toupper" is pressed
17 | document.getElementById('toupper').addEventListener('click', upperCase);
18 |
19 | function moreCowbell() {
20 | // get the current value of the text inside the textarea with ID "cupcakeinput"
21 | var cupcakeInput = document.getElementById("cupcakeinput").value;
22 | var cowbellArray = cupcakeInput.split(' ');
23 |
24 | // add in the cowbell
25 | var cowbellCupakeText = "";
26 | for (var i = 0; i < cowbellArray.length; i++){
27 | cowbellCupakeText += cowbellArray[i] + ' cowbell ';
28 | }
29 | document.getElementById("cupcakeinput").value = cowbellCupakeText;
30 | }
31 |
32 | // run moreCowbell function when button with ID "morecowbell" is pressed
33 | document.getElementById('morecowbell').addEventListener('click', moreCowbell);
--------------------------------------------------------------------------------
/day4/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 30 Days of Web Dev - Day 4 - Strings
5 |
6 |
7 |
8 |
9 |
16 |
There's no better way to celebrate Easter than to make a decimal-binary-hexadecimal converter, amirite?! ;) Today I learned how to use the keyup event with JavaScript's addEventListener() function and how to use parseInt() and toString() to convert between different numeral systems.
18 |
19 |
Decimal:
20 |
Binary:
21 |
Hexadecimal:
22 |
23 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/day5/mathstuff.js:
--------------------------------------------------------------------------------
1 | // Run my convertFromDecimal() function when something is typed in the input box with id "decimal"
2 | document.getElementById('decimal').addEventListener('keyup', convertFromDecimal);
3 |
4 | function convertFromDecimal() {
5 | // get the user's input and convert from a string to an integer in base 10 (decimal)
6 | var decimalInput = parseInt(document.getElementById("decimal").value, 10);
7 |
8 | // use parseInt again to convert from decimal integer to binary string representation
9 | document.getElementById("binary").value = decimalInput.toString(2);
10 |
11 | // use toString to convert from binary integer to its hexadecimal string representation
12 | document.getElementById("hexadecimal").value = decimalInput.toString(16).toUpperCase();
13 | }
14 |
15 | // Run convertFromBinary() function when something is typed in the input box with id "binary"
16 | document.getElementById('binary').addEventListener('keyup', convertFromBinary);
17 |
18 | function convertFromBinary() {
19 | // get the user's input and convert from a string to an integer in base 2 (binary)
20 | var binaryInput = parseInt(document.getElementById("binary").value, 2);
21 |
22 | // use toString to convert from binary integer to decimal string representation
23 | document.getElementById("decimal").value = binaryInput.toString(10);
24 |
25 | // use toString to convert from binary integer to its hexadecimal string representation
26 | document.getElementById("hexadecimal").value = binaryInput.toString(16).toUpperCase();
27 | }
28 |
29 | // Run my convertNumber() function when something is typed in the input box with id "hexadecimal"
30 | document.getElementById('hexadecimal').addEventListener('keyup', convertFromHexadecimal);
31 |
32 | function convertFromHexadecimal() {
33 | // get the user's input and convert from a string to an integer in base 16 (hexadecimal)
34 | var hexInput = parseInt(document.getElementById("hexadecimal").value, 16);
35 |
36 | // use toString to convert from hexadecimal integer to decimal string representation
37 | document.getElementById("decimal").value = hexInput.toString(10);
38 |
39 | // use toString to convert from hexadecimal integer to its binary string representation
40 | document.getElementById("binary").value = hexInput.toString(2);
41 | }
--------------------------------------------------------------------------------
/day5/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 60px auto;
3 | width: 70%;
4 | max-width: 950px;
5 | }
6 | nav ul, footer ul {
7 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
8 | padding: 0px;
9 | list-style: none;
10 | font-weight: bold;
11 | }
12 | nav ul li, footer ul li {
13 | display: inline;
14 | margin-right: 20px;
15 | }
16 | a {
17 | text-decoration: none;
18 | color: #999;
19 | }
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 | h1 {
24 | font-size: 3em;
25 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
26 | }
27 | p, button, input {
28 | font-size: 1.5em;
29 | line-height: 1.4em;
30 | color: #333;
31 | margin-bottom:1em;
32 | }
33 | footer {
34 | clear:both;
35 | border-top: 1px solid #d5d5d5;
36 | font-size: .8em;
37 | }
38 |
39 | ul.posts {
40 | margin: 20px auto 40px;
41 | font-size: 1.5em;
42 | }
43 |
44 | ul.posts li {
45 | list-style: none;
46 | }
47 |
48 | .inputbox {
49 | float:left;
50 | width:33%;
51 | }
52 |
53 | .inputbox p {
54 | margin-bottom:0.5em;
55 | margin-top: 0;
56 | }
57 |
58 | #cupcakeinput {
59 | width: 100%;
60 | height:200px;
61 | font-family: 'Georgia', 'Times New Roman', serif;
62 | text-shadow: 2px 2px 7px rgba(255, 255, 255, 1);
63 | background: #fcecfc; /* Old browsers */
64 | background: -moz-linear-gradient(top, #fcecfc 0%, #fba6e1 52%, #fd89d7 74%, #ff7cd8 100%); /* FF3.6+ */
65 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#fcecfc), color-stop(52%,#fba6e1), color-stop(74%,#fd89d7), color-stop(100%,#ff7cd8)); /* Chrome,Safari4+ */
66 | background: -webkit-linear-gradient(top, #fcecfc 0%,#fba6e1 52%,#fd89d7 74%,#ff7cd8 100%); /* Chrome10+,Safari5.1+ */
67 | background: -o-linear-gradient(top, #fcecfc 0%,#fba6e1 52%,#fd89d7 74%,#ff7cd8 100%); /* Opera 11.10+ */
68 | background: -ms-linear-gradient(top, #fcecfc 0%,#fba6e1 52%,#fd89d7 74%,#ff7cd8 100%); /* IE10+ */
69 | background: linear-gradient(to bottom, #fcecfc 0%,#fba6e1 52%,#fd89d7 74%,#ff7cd8 100%); /* W3C */
70 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fcecfc', endColorstr='#ff7cd8',GradientType=0 ); /* IE6-9 */
71 |
72 | }
--------------------------------------------------------------------------------
/day6/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 30 Days of Web Dev - Day 6 - Time Flies
5 |
6 |
7 |
8 |
9 |
10 |
17 |
You've been wasting your life on this web page for 0 seconds.
20 |
WHY?!?!?! Go read about the JavaScript Date object and timer functions instead. That's where I learned how to make this super simple timer in no time!
21 |
22 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/day6/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 60px auto;
3 | width: 70%;
4 | max-width: 950px;
5 | }
6 | nav ul, footer ul {
7 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
8 | padding: 0px;
9 | list-style: none;
10 | font-weight: bold;
11 | }
12 | nav ul li, footer ul li {
13 | display: inline;
14 | margin-right: 20px;
15 | }
16 | a {
17 | text-decoration: none;
18 | color: #999;
19 | }
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 | h1 {
24 | font-size: 3em;
25 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
26 | }
27 | p, button, input {
28 | font-size: 1.5em;
29 | line-height: 1.4em;
30 | color: #333;
31 | margin-bottom:1em;
32 | }
33 | footer {
34 | clear:both;
35 | border-top: 1px solid #d5d5d5;
36 | font-size: .8em;
37 | }
38 |
39 | ul.posts {
40 | margin: 20px auto 40px;
41 | font-size: 1.5em;
42 | }
43 |
44 | ul.posts li {
45 | list-style: none;
46 | }
47 |
48 | #timer {
49 | font-family: 'Audiowide', sans-serif;
50 | color: #13BF1F;
51 | }
--------------------------------------------------------------------------------
/day6/timeflies.js:
--------------------------------------------------------------------------------
1 | // get the exact time when the web page loaded
2 | var start = Date.now();
3 |
4 | // run the updateTime() function every 500 milliseconds
5 | var intervalID = setInterval(updateTime,500);
6 |
7 | function updateTime() {
8 |
9 | var timerString = '';
10 |
11 | // subtract current time from start time and round down (no decimal points)
12 | var secondsPassed = Math.floor((Date.now() - start) / 1000)
13 | var minutesPassed = Math.floor(secondsPassed / 60);
14 | var secondsRemainder = Math.floor(secondsPassed % 60);
15 |
16 | // say "1 minute" singular, but then use "minutes" plural
17 | if (minutesPassed == 1) {
18 | timerString += minutesPassed + ' minute';
19 | } else if (minutesPassed > 1){
20 | timerString += minutesPassed + ' minutes';
21 | }
22 |
23 | if (minutesPassed >= 1 && secondsRemainder >= 1) {
24 | timerString += ' and ';
25 | }
26 |
27 | // so the timer doesn't disappear before the first second elapses:
28 | if (secondsPassed == 0) {
29 | timerString += secondsPassed + ' seconds';
30 | }
31 |
32 | // say "1 second" singular, but then use plural "Seconds"
33 | if (secondsRemainder == 1) {
34 | timerString += secondsRemainder + ' second';
35 | } else if (secondsRemainder > 1){
36 | timerString += secondsRemainder + ' seconds';
37 | }
38 |
39 | // put the timer info into the HTML
40 | document.getElementById('timer').innerHTML = timerString;
41 | }
--------------------------------------------------------------------------------
/day7/drawgrid.js:
--------------------------------------------------------------------------------
1 | // run drawGrid() each time the HTML element with ID startbtn is pressed
2 | document.getElementById('startbtn').addEventListener('click', drawGrid);
3 |
4 | function drawGrid() {
5 | //reset gridcontainer each time button is pressed
6 | document.getElementById('gridcontainer').innerHTML = '';
7 |
8 | // get user input and convert to integers
9 | var rows = parseInt(document.getElementById('numrows').value, 10);
10 | var cols = parseInt(document.getElementById('numcols').value, 10);
11 |
12 | // get the current width of gridcontainer (this changes with screen or window size)
13 | var gridWidth = document.getElementById('gridcontainer').offsetWidth;
14 |
15 | // Make every square of the grid the same size and make them scaled based on size of gridcontainer
16 | var squareSize = gridWidth / cols;
17 |
18 | // the nested loop! iterate through both columns and rows
19 | for (i = 0; i < cols; i++) {
20 | for (j = 0; j < rows; j++) {
21 |
22 | // create a new HTML element for each grid square and make it the right size
23 | var square = document.createElement("div");
24 | document.getElementById("gridcontainer").appendChild(square);
25 | square.style.width = squareSize + 'px';
26 | square.style.height = squareSize + 'px';
27 |
28 | // set each grid square's coordinates: mutliples of the current row or column number
29 | var topPosition = j * squareSize;
30 | var leftPosition = i * squareSize;
31 |
32 | // use CSS absolute positioning to place each grid square on the page
33 | square.style.top = topPosition + 'px';
34 | square.style.left = leftPosition + 'px';
35 |
36 | // give every other square a different class (for a different color)
37 | if ((i + j) % 2 == 0) {
38 | square.className = 'evencolor';
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/day7/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 30 Days of Web Dev - Day 7 - Loop de Loop
5 |
6 |
7 |
8 |
9 |
10 |
17 |
Loop de Loop
18 |
Today I played around with nested loops (a loop inside another loop!) to generate a checkerboard-style grid. Read more about for loops. Oh, and of course the free online book Eloquent JavaScript is a must-read!
There's so much you can do with web typography! Today I just played a bit with Google Fonts, which didn't exist when I first learned about web design! The text is just vanilla lorem ipsum, and the CSS for the old-timey drop cap is from CSS Tricks. (I was hoping to play with more font properties but I ran out of time today!)
23 |
24 |
25 |
26 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed aliquet dignissim dapibus. Curabitur luctus vehicula ligula, vel pharetra ante pulvinar et. Suspendisse vel metus odio. Sed massa ipsum, tempor ut nulla eget, pharetra condimentum risus. In ullamcorper tortor sed elit tincidunt, non blandit augue eleifend. Phasellus sit amet nulla nec nulla suscipit eleifend. In nisi lacus, hendrerit id tellus et, elementum eleifend justo. Donec a dapibus tortor. Cras ac lectus pharetra, iaculis turpis ac, efficitur nunc. Vestibulum sapien magna, dapibus ut tincidunt sed, elementum ultricies tellus. Vestibulum varius nisl vel cursus vulputate. Aliquam porttitor scelerisque lorem non placerat. Nullam interdum, diam eu scelerisque scelerisque, massa libero malesuada libero, in maximus tortor risus malesuada eros. Proin rhoncus dictum dolor, eu convallis libero consequat vitae. In tincidunt massa sit amet lacus mattis porttitor eu a magna. Nulla suscipit turpis nisi, a mollis leo elementum a.
27 |
28 |
Nulla neque lorem, molestie in faucibus eu, rutrum et dui. Cras condimentum, lacus ut hendrerit euismod, sapien turpis vehicula urna, sit amet venenatis turpis turpis quis leo. Sed laoreet lectus at metus egestas, at iaculis neque elementum. Morbi non diam ac ligula luctus mattis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi consequat commodo nunc ut sagittis. Nullam eu nibh vitae ligula tempor posuere eu nec est. Praesent placerat mollis lorem, nec interdum augue sollicitudin a. Maecenas vel tortor auctor, finibus dui mollis, placerat velit. Nulla facilisis interdum laoreet. Sed laoreet ipsum sed porta vestibulum. Nam pellentesque pulvinar magna, id sagittis urna lobortis eget. Vivamus aliquam sem ipsum, nec aliquam velit mollis at. Quisque nec laoreet est. Morbi sed consectetur purus, sed congue libero. Praesent orci lacus, elementum in magna vestibulum, viverra tincidunt neque.
29 |
30 |
Nam velit arcu, ultricies sagittis justo ac, ullamcorper sagittis dui. Proin id enim quis orci condimentum dignissim at eget purus. Fusce et nisi urna. Vivamus in tincidunt magna. Aliquam mattis justo est, non sagittis nisi dignissim nec. Maecenas auctor ut leo rutrum placerat. Pellentesque aliquam enim at sem rutrum, sit amet ultrices lacus dapibus. Quisque a scelerisque leo. Pellentesque id ex enim. Fusce scelerisque lacus placerat venenatis convallis. In ut elit aliquet odio tincidunt efficitur. In ut vehicula enim. Quisque sed tellus sagittis, efficitur est vitae, porta lectus.
31 |
32 |
33 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/day8/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 60px auto;
3 | width: 70%;
4 | max-width: 950px;
5 | }
6 | nav ul, footer ul {
7 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
8 | padding: 0px;
9 | list-style: none;
10 | font-weight: bold;
11 | }
12 | nav ul li, footer ul li {
13 | display: inline;
14 | margin-right: 20px;
15 | }
16 | a {
17 | text-decoration: none;
18 | color: #999;
19 | }
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 | h1 {
24 | font-size: 3em;
25 | font-family:'Helvetica', 'Arial', 'Sans-Serif';
26 | }
27 | p, button, input {
28 | font-size: 1.5em;
29 | line-height: 1.4em;
30 | color: #333;
31 | margin-bottom:1em;
32 | }
33 | footer {
34 | clear:both;
35 | border-top: 1px solid #d5d5d5;
36 | font-size: .8em;
37 | }
38 |
39 | ul.posts {
40 | margin: 20px auto 40px;
41 | font-size: 1.5em;
42 | }
43 |
44 | ul.posts li {
45 | list-style: none;
46 | }
47 |
48 | div p {
49 | font-family:'Georgia', 'Times New Roman', serif;
50 | }
51 |
52 | .digital p {
53 | font-family: 'Press Start 2P', cursive;
54 | font-size:1.2em;
55 | }
56 |
57 | .handdrawn p {
58 | font-family: 'Indie Flower', cursive;
59 | }
60 |
61 | .modern p {
62 | font-family: 'Open Sans Condensed', sans-serif;
63 | }
64 |
65 | .oldtimey p {
66 | font-family: 'Old Standard TT', serif;
67 | }
68 |
69 | .oldtimey p:first-child:first-letter {
70 | float: left;
71 | color: #903;
72 | font-size: 75px;
73 | line-height: 60px;
74 | padding-top: 4px;
75 | padding-right: 8px;
76 | padding-left: 3px;
77 | }
78 |
--------------------------------------------------------------------------------
/day8/typographystuff.js:
--------------------------------------------------------------------------------
1 | // run corresponding function when button is clicked
2 | document.getElementById('oldtimey').addEventListener('click', oldtimey);
3 |
4 | // add CSS class corresponding to the button pressed
5 | function oldtimey() {
6 | document.getElementById('custom').className = 'oldtimey';
7 | }
8 |
9 | // run corresponding function when button is clicked
10 | document.getElementById('modern').addEventListener('click', modern);
11 |
12 | // add CSS class corresponding to the button pressed
13 | function modern() {
14 | document.getElementById('custom').className = 'modern';
15 | }
16 |
17 | // run corresponding function when button is clicked
18 | document.getElementById('handdrawn').addEventListener('click', handdrawn);
19 |
20 | // add CSS class corresponding to the button pressed
21 | function handdrawn() {
22 | document.getElementById('custom').className = 'handdrawn';
23 | }
24 |
25 | // run corresponding function when button is clicked
26 | document.getElementById('digital').addEventListener('click', digital);
27 |
28 | // add CSS class corresponding to the button pressed
29 | function digital() {
30 | document.getElementById('custom').className = 'digital';
31 | }
32 |
--------------------------------------------------------------------------------
/day9/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 30 Days of Web Dev - Day 9 - Responsive Design
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
20 |
Responsive Design
21 |
22 |
Today I spent a few minutes learning about the magic of CSS media queries to make a tiny responsive design! Resize your browser window to see the magic happen. (OK, it's really not that exciting. But this is just the beginning!)
I'm learning about web development by creating a website or web app every day for 30 days. Wish me luck! You can see all the code on GitHub and blog posts/animated GIFs on Google+.