├── .gitignore ├── Gemfile ├── Gemfile.lock ├── README.md ├── _config.yml ├── _layouts └── default.html ├── day1 ├── index.html └── style.css ├── day11 ├── drawclickygrid.js ├── index.html └── style.css ├── day12 ├── battleship.js ├── index.html └── style.css ├── day13 ├── battleship.js ├── index.html └── style.css ├── day14 ├── battleship.js ├── index.html └── style.css ├── day15 ├── battleship.js ├── index.html └── style.css ├── day17 ├── battleship.js ├── index.html └── style.css ├── day18 ├── index.html └── style.css ├── day2 ├── fizzbuzz.js ├── index.html └── style.css ├── day20 ├── battleship.js ├── index.html └── style.css ├── day21 ├── battleship.js ├── index.html └── style.css ├── day23 ├── battleship.js ├── index.html └── style.css ├── day24 ├── index.html └── style.css ├── day25 └── index.md ├── day26 ├── 24px.png ├── index.html └── style.css ├── day27 ├── index.html └── timer.js ├── day28 ├── index.html └── timer.js ├── day29 ├── ding.wav ├── index.html └── timer.js ├── day3 ├── index.html └── style.css ├── day4 ├── cupcakestrings.js ├── index.html └── style.css ├── day5 ├── index.html ├── mathstuff.js └── style.css ├── day6 ├── index.html ├── style.css └── timeflies.js ├── day7 ├── drawgrid.js ├── index.html └── style.css ├── day8 ├── index.html ├── style.css └── typographystuff.js ├── day9 ├── index.html └── style.css ├── index.html ├── pygments.css └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | _site/ 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'github-pages' 3 | gem 'wdm', '>= 0.1.0' if Gem.win_platform? 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | RedCloth (4.2.9) 5 | activesupport (4.2.1) 6 | i18n (~> 0.7) 7 | json (~> 1.7, >= 1.7.7) 8 | minitest (~> 5.1) 9 | thread_safe (~> 0.3, >= 0.3.4) 10 | tzinfo (~> 1.1) 11 | blankslate (2.1.2.4) 12 | celluloid (0.16.0) 13 | timers (~> 4.0.0) 14 | classifier-reborn (2.0.3) 15 | fast-stemmer (~> 1.0) 16 | coffee-script (2.4.1) 17 | coffee-script-source 18 | execjs 19 | coffee-script-source (1.9.1.1) 20 | colorator (0.1) 21 | execjs (2.5.2) 22 | fast-stemmer (1.0.2) 23 | ffi (1.9.8-x64-mingw32) 24 | gemoji (2.1.0) 25 | github-pages (34) 26 | RedCloth (= 4.2.9) 27 | github-pages-health-check (~> 0.2) 28 | jekyll (= 2.4.0) 29 | jekyll-coffeescript (= 1.0.1) 30 | jekyll-mentions (= 0.2.1) 31 | jekyll-redirect-from (= 0.6.2) 32 | jekyll-sass-converter (= 1.2.0) 33 | jekyll-sitemap (= 0.8.1) 34 | jemoji (= 0.4.0) 35 | kramdown (= 1.5.0) 36 | liquid (= 2.6.1) 37 | maruku (= 0.7.0) 38 | mercenary (~> 0.3) 39 | pygments.rb (= 0.6.1) 40 | rdiscount (= 2.1.7) 41 | redcarpet (= 3.1.2) 42 | terminal-table (~> 1.4) 43 | github-pages-health-check (0.3.0) 44 | net-dns (~> 0.6) 45 | public_suffix (~> 1.4) 46 | hitimes (1.2.2) 47 | html-pipeline (1.9.0) 48 | activesupport (>= 2) 49 | nokogiri (~> 1.4) 50 | i18n (0.7.0) 51 | jekyll (2.4.0) 52 | classifier-reborn (~> 2.0) 53 | colorator (~> 0.1) 54 | jekyll-coffeescript (~> 1.0) 55 | jekyll-gist (~> 1.0) 56 | jekyll-paginate (~> 1.0) 57 | jekyll-sass-converter (~> 1.0) 58 | jekyll-watch (~> 1.1) 59 | kramdown (~> 1.3) 60 | liquid (~> 2.6.1) 61 | mercenary (~> 0.3.3) 62 | pygments.rb (~> 0.6.0) 63 | redcarpet (~> 3.1) 64 | safe_yaml (~> 1.0) 65 | toml (~> 0.1.0) 66 | jekyll-coffeescript (1.0.1) 67 | coffee-script (~> 2.2) 68 | jekyll-gist (1.2.1) 69 | jekyll-mentions (0.2.1) 70 | html-pipeline (~> 1.9.0) 71 | jekyll (~> 2.0) 72 | jekyll-paginate (1.1.0) 73 | jekyll-redirect-from (0.6.2) 74 | jekyll (~> 2.0) 75 | jekyll-sass-converter (1.2.0) 76 | sass (~> 3.2) 77 | jekyll-sitemap (0.8.1) 78 | jekyll-watch (1.2.1) 79 | listen (~> 2.7) 80 | jemoji (0.4.0) 81 | gemoji (~> 2.0) 82 | html-pipeline (~> 1.9) 83 | jekyll (~> 2.0) 84 | json (1.8.1) 85 | kramdown (1.5.0) 86 | liquid (2.6.1) 87 | listen (2.10.0) 88 | celluloid (~> 0.16.0) 89 | rb-fsevent (>= 0.9.3) 90 | rb-inotify (>= 0.9) 91 | maruku (0.7.0) 92 | mercenary (0.3.5) 93 | mini_portile (0.6.2) 94 | minitest (5.6.0) 95 | net-dns (0.8.0) 96 | nokogiri (1.6.6.2-x64-mingw32) 97 | mini_portile (~> 0.6.0) 98 | parslet (1.5.0) 99 | blankslate (~> 2.0) 100 | posix-spawn (0.3.11) 101 | public_suffix (1.5.1) 102 | pygments.rb (0.6.1) 103 | posix-spawn (~> 0.3.6) 104 | yajl-ruby (~> 1.2.0) 105 | rb-fsevent (0.9.4) 106 | rb-inotify (0.9.5) 107 | ffi (>= 0.5.0) 108 | rdiscount (2.1.7) 109 | redcarpet (3.1.2) 110 | safe_yaml (1.0.4) 111 | sass (3.4.10) 112 | terminal-table (1.4.5) 113 | thread_safe (0.3.5) 114 | timers (4.0.1) 115 | hitimes 116 | toml (0.1.2) 117 | parslet (~> 1.5.0) 118 | tzinfo (1.2.2) 119 | thread_safe (~> 0.1) 120 | wdm (0.1.0) 121 | yajl-ruby (1.2.1) 122 | 123 | PLATFORMS 124 | x64-mingw32 125 | 126 | DEPENDENCIES 127 | github-pages 128 | wdm (>= 0.1.0) 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 30DaysOfWebDev 2 | I'm learning about web development by creating a website or web app every day for 30 days. Wish me luck! I'll link to everything I make on this page as I continue learning. 3 | 4 | Website: http://learningnerd.github.io/30DaysOfWebDev/ 5 | 6 | - [Day 1: GitHub Pages](http://learningnerd.github.io/30DaysOfWebDev/day1/) 7 | - [Day 2: Fizz Buzz!] (http://learningnerd.github.io/30DaysOfWebDev/day2/) 8 | - [Day 3: CSS Floats] (http://learningnerd.github.io/30DaysOfWebDev/day3/) 9 | - [Day 4: Strings] (http://learningnerd.github.io/30DaysOfWebDev/day4/) 10 | - [Day 5: A Little Math] (http://learningnerd.github.io/30DaysOfWebDev/day5/) 11 | - [Day 6: Time Flies] (http://learningnerd.github.io/30DaysOfWebDev/day6/) 12 | - [Day 7: Loop de Loop] (http://learningnerd.github.io/30DaysOfWebDev/day7/) 13 | - [Day 8: Fun with Typography] (http://learningnerd.github.io/30DaysOfWebDev/day8/) 14 | - [Day 9: Responsive Design] (http://learningnerd.github.io/30DaysOfWebDev/day9/) 15 | - Day 10 - Missed a day. Shame on me! (But not that big a deal.) 16 | - [Day 11: The Game is Afoot] (http://learningnerd.github.io/30DaysOfWebDev/day11/) 17 | - [Day 12: Battleship] (http://learningnerd.github.io/30DaysOfWebDev/day12/) 18 | - [Day 13: Battleship Objects] (http://learningnerd.github.io/30DaysOfWebDev/day13/) 19 | - [Day 14: Recursive Randomness] (http://learningnerd.github.io/30DaysOfWebDev/day14/) 20 | - [Day 15: Working Battleship] (http://learningnerd.github.io/30DaysOfWebDev/day15/) 21 | - Day 16 - Missed another day. Oops! But I still learned a lot. 22 | - [Day 17: Basic Working Battleship!] (http://learningnerd.github.io/30DaysOfWebDev/day17/) 23 | - [Day 18: Ruby on Rails] (http://learningnerd.github.io/30DaysOfWebDev/day18/) 24 | - Day 19 - Missed another day. Too exhausted from Rails Girls yesterday! 25 | - [Day 20: Animated Algorithm] (http://learningnerd.github.io/30DaysOfWebDev/day20/) 26 | - [Day 21: More Algorithmic Adventures] (http://learningnerd.github.io/30DaysOfWebDev/day21/) 27 | - Day 22 - Missed yet another day. I was just lazy. =P 28 | - [Day 23: Final Algorithm] (http://learningnerd.github.io/30DaysOfWebDev/day23/) 29 | - [Day 24: CSS Layouts] (http://learningnerd.github.io/30DaysOfWebDev/day24/) 30 | - [Day 25: Using Jekyll] (http://learningnerd.github.io/30DaysOfWebDev/day25/) 31 | - [Day 26: Vertical Rhythm] (http://learningnerd.github.io/30DaysOfWebDev/day26/) 32 | - [Day 27: Countdown Timer] (http://learningnerd.github.io/30DaysOfWebDev/day27/) 33 | - [Day 28: Local Web Storage] (http://learningnerd.github.io/30DaysOfWebDev/day28/) 34 | - [Day 29: Better Pomodoro Timer] (http://learningnerd.github.io/30DaysOfWebDev/day29/) 35 | - Day 30 - D'aww, missed the last day! I think I got burned out. 36 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | name: 30 Days of Web Dev 2 | markdown: kramdown 3 | highlighter: pygments 4 | baseurl: /30DaysOfWebDev 5 | -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ page.title }} 5 | 6 | 7 | 8 | 9 | 10 | 17 | {{ content }} 18 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /day1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 30 Days of Web Dev - Day 1 - GitHub Pages 5 | 6 | 7 | 8 | 9 | 16 |

Hello! This is Liz's awesome first GitHub Pages site.

17 |

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 my daily projects here.

18 |

I built this GitHub site by following this tutorial by Jonathan McGlone and this tutorial by Anna Debenham. 19 |

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.

23 |

I didn't get very far, but today I learned how to use JavaScript to handle events for many elements, and I discovered the incredibly useful box-sizing property in CSS. Click on the grid below and see what happens! (Better yet, look at the code!)

24 | 25 |
26 |
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 |

Battleship Objects

21 | 22 |

Still far from finished with this, but I rewrote a lot of yesterday's code using object-oriented JavaScript. The output is basically the same, but the code looks really different now!

23 |
24 | 25 | 26 |
27 | 28 |
29 |
30 |
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...

19 |

20 |
21 |
22 |
23 | 24 |
25 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /day17/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 | } -------------------------------------------------------------------------------- /day18/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 30 Days of Web Dev - Day 18 - Ruby on Rails 5 | 6 | 7 | 8 | 9 | 16 |

Ruby on Rails

17 | 18 |

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 | 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!

    27 |

    28 |
    29 |
    30 |
    31 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /day23/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 | .container { 52 | width:500px; 53 | margin-right:10px; 54 | } 55 | 56 | #gameboard { 57 | float:right; 58 | margin: 0; 59 | position:relative; 60 | } 61 | 62 | #gameboard div { 63 | position:absolute; 64 | -webkit-box-sizing: border-box; /* Safari 3.0 - 5.0, Chrome 1 - 9, Android 2.1 - 3.x */ 65 | -moz-box-sizing: border-box; /* Firefox 1 - 28 */ 66 | box-sizing: border-box; /* Safari 5.1+, Chrome 10+, Firefox 29+, Opera 7+, IE 8+, Android 4.0+, iOS any */ 67 | background: #f6f8f9; /* Old browsers */ 68 | border: 1px solid #ddd; 69 | } 70 | 71 | #messages { 72 | float:left; 73 | margin-right:1em; 74 | max-width:425px; 75 | } 76 | 77 | @media (max-width: 1350px) { 78 | #messages { 79 | max-width:400px; 80 | } 81 | } 82 | @media (max-width: 1300px) { 83 | #messages { 84 | max-width:370px; 85 | } 86 | } 87 | @media (max-width: 1240px) { 88 | #messages { 89 | max-width:950px; 90 | text-align:center; 91 | } 92 | .message { 93 | text-align:center; 94 | } 95 | } 96 | #btns { 97 | text-align:center; 98 | } 99 | 100 | .message { 101 | margin-top:0; 102 | font-family:'Helvetica', 'Arial', 'Sans-Serif'; 103 | color: #333; 104 | } -------------------------------------------------------------------------------- /day24/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 30 Days of Web Dev - Day 24 - CSS Layouts 5 | 6 | 7 | 8 | 9 | 16 |

    CSS Layouts

    17 |
    18 |

    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 | 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.

    20 |

    30 Days of Web Dev | GitHub 21 | 22 | 23 | -------------------------------------------------------------------------------- /day26/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 2em auto; 3 | width:90%; 4 | max-width: 900px; 5 | font-family:'Georgia', serif; 6 | font-size: 20px; 7 | background: #fff url(24px.png); 8 | } 9 | 10 | p { 11 | font-size: 1em; 12 | line-height:1.2em; 13 | margin-top: 1.2em; 14 | margin-bottom:1.2em; 15 | } 16 | 17 | h1 { 18 | font-size:1.8em; 19 | line-height:1em; 20 | margin-top:0.5em; 21 | margin-bottom:0.5em; 22 | } 23 | h2 { 24 | font-size:1.2em; 25 | line-height:1em; 26 | margin-top:1em; 27 | margin-bottom:1em; 28 | } -------------------------------------------------------------------------------- /day27/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Day 27 - Countdown Timer 4 | javascriptfile: timer 5 | --- 6 |

    Countdown Timer

    7 | 8 |

    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 |

    Strings

    17 |

    I'm so sleepy today! But I still spent a few minutes learning about some of JavaScript's string functions like toLowerCase() and toUpperCase(), split() with some cupcake ipsum:

    18 |
    19 | 20 | 21 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /day4/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, textarea { 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 | #cupcakeinput { 49 | width: 100%; 50 | height:200px; 51 | font-family: 'Georgia', 'Times New Roman', serif; 52 | text-shadow: 2px 2px 7px rgba(255, 255, 255, 1); 53 | background: #fcecfc; /* Old browsers */ 54 | background: -moz-linear-gradient(top, #fcecfc 0%, #fba6e1 52%, #fd89d7 74%, #ff7cd8 100%); /* FF3.6+ */ 55 | 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+ */ 56 | background: -webkit-linear-gradient(top, #fcecfc 0%,#fba6e1 52%,#fd89d7 74%,#ff7cd8 100%); /* Chrome10+,Safari5.1+ */ 57 | background: -o-linear-gradient(top, #fcecfc 0%,#fba6e1 52%,#fd89d7 74%,#ff7cd8 100%); /* Opera 11.10+ */ 58 | background: -ms-linear-gradient(top, #fcecfc 0%,#fba6e1 52%,#fd89d7 74%,#ff7cd8 100%); /* IE10+ */ 59 | background: linear-gradient(to bottom, #fcecfc 0%,#fba6e1 52%,#fd89d7 74%,#ff7cd8 100%); /* W3C */ 60 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fcecfc', endColorstr='#ff7cd8',GradientType=0 ); /* IE6-9 */ 61 | 62 | } -------------------------------------------------------------------------------- /day5/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 30 Days of Web Dev - Day 5 - A Little Math 5 | 6 | 7 | 8 | 9 | 16 |

    A Little Math

    17 |

    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 |

    Time Flies

    18 |

    "Time flies like an arrow; fruit flies like a banana." (For more, see this awesome Wikipedia entry.)

    19 |

    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!

    19 | 20 | 21 | 22 | 23 |
    24 | 25 | 26 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /day7/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 60px auto; 3 | width: 70%; 4 | max-width: 950px; 5 | } 6 | 7 | nav ul, footer ul { 8 | font-family:'Helvetica', 'Arial', 'Sans-Serif'; 9 | padding: 0px; 10 | list-style: none; 11 | font-weight: bold; 12 | } 13 | 14 | nav ul li, footer ul li { 15 | display: inline; 16 | margin-right: 20px; 17 | } 18 | 19 | a { 20 | text-decoration: none; 21 | color: #999; 22 | } 23 | 24 | a:hover { 25 | text-decoration: underline; 26 | } 27 | 28 | h1 { 29 | font-size: 3em; 30 | font-family:'Helvetica', 'Arial', 'Sans-Serif'; 31 | } 32 | 33 | p, button, input { 34 | font-size: 1.5em; 35 | line-height: 1.4em; 36 | color: #333; 37 | margin-bottom:1em; 38 | } 39 | 40 | footer { 41 | clear:both; 42 | border-top: 1px solid #d5d5d5; 43 | font-size: .8em; 44 | } 45 | 46 | ul.posts { 47 | margin: 20px auto 40px; 48 | font-size: 1.5em; 49 | } 50 | 51 | ul.posts li { 52 | list-style: none; 53 | } 54 | 55 | #gridcontainer { 56 | max-width:500px; 57 | margin: 0 auto; 58 | z-index:-1; 59 | position:relative; 60 | background: #ddd; 61 | } 62 | 63 | #gridcontainer div { 64 | position:absolute; 65 | background: #f6f8f9; /* Old browsers */ 66 | background: -moz-linear-gradient(top, #f6f8f9 0%, #e5ebee 50%, #d7dee3 51%, #f5f7f9 100%); /* FF3.6+ */ 67 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f6f8f9), color-stop(50%,#e5ebee), color-stop(51%,#d7dee3), color-stop(100%,#f5f7f9)); /* Chrome,Safari4+ */ 68 | background: -webkit-linear-gradient(top, #f6f8f9 0%,#e5ebee 50%,#d7dee3 51%,#f5f7f9 100%); /* Chrome10+,Safari5.1+ */ 69 | background: -o-linear-gradient(top, #f6f8f9 0%,#e5ebee 50%,#d7dee3 51%,#f5f7f9 100%); /* Opera 11.10+ */ 70 | background: -ms-linear-gradient(top, #f6f8f9 0%,#e5ebee 50%,#d7dee3 51%,#f5f7f9 100%); /* IE10+ */ 71 | background: linear-gradient(to bottom, #f6f8f9 0%,#e5ebee 50%,#d7dee3 51%,#f5f7f9 100%); /* W3C */ 72 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f6f8f9', endColorstr='#f5f7f9',GradientType=0 ); /* IE6-9 */ 73 | } 74 | 75 | #gridcontainer div.evencolor { 76 | background: #ebe9f9; /* Old browsers */ 77 | background: -moz-linear-gradient(top, #ebe9f9 0%, #d8d0ef 50%, #cec7ec 51%, #c1bfea 100%); /* FF3.6+ */ 78 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ebe9f9), color-stop(50%,#d8d0ef), color-stop(51%,#cec7ec), color-stop(100%,#c1bfea)); /* Chrome,Safari4+ */ 79 | background: -webkit-linear-gradient(top, #ebe9f9 0%,#d8d0ef 50%,#cec7ec 51%,#c1bfea 100%); /* Chrome10+,Safari5.1+ */ 80 | background: -o-linear-gradient(top, #ebe9f9 0%,#d8d0ef 50%,#cec7ec 51%,#c1bfea 100%); /* Opera 11.10+ */ 81 | background: -ms-linear-gradient(top, #ebe9f9 0%,#d8d0ef 50%,#cec7ec 51%,#c1bfea 100%); /* IE10+ */ 82 | background: linear-gradient(to bottom, #ebe9f9 0%,#d8d0ef 50%,#cec7ec 51%,#c1bfea 100%); /* W3C */ 83 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ebe9f9', endColorstr='#c1bfea',GradientType=0 ); /* IE6-9 */ 84 | } 85 | -------------------------------------------------------------------------------- /day8/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 30 Days of Web Dev - Day 8 - Fun with Typography 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 |

    Fun with Typography

    21 | 22 |

    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!)

    23 | 24 |
    25 |

    Cheese Ipsum

    26 |

    Airedale manchego pepper jack. Cheesy feet cheesecake stilton cheese slices mascarpone cheddar stilton boursin. Edam macaroni cheese monterey jack cheesecake mozzarella port-salut stinking bishop ricotta.

    27 |
    28 | 29 |
    30 |

    Gangsta Lorem Ipsum

    31 |

    Lorem ipsizzle dolizzle sit amet, consectetuer adipiscing shizznit. Nullam sapizzle velizzle, check out this volutpizzle, suscipit bow wow wow, fo shizzle vizzle, arcu. Pellentesque funky fresh tortor.

    32 |
    33 | 34 |
    35 |

    Hipster Ipsum

    36 |

    Flannel chia High Life, literally 3 wolf moon viral blog leggins. Roof party Odd Future listicle synth. Messenger bag ennui, bitters Wes Anderson mustache Tumblr retro meggings iPhone sriracha, +1 YOLO.

    37 |
    38 | 39 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /day9/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 | .box { 49 | float:left; 50 | width:30%; 51 | padding:0 1.5% 0 1.5%; 52 | font-size:0.9em; 53 | } 54 | 55 | @media (max-width: 1100px) { 56 | .box{ 57 | width:45%; 58 | padding: 0 2.5% 0 2.5%; 59 | } 60 | .boxthree{ 61 | width:100%; 62 | padding: 0; 63 | } 64 | } 65 | 66 | @media (max-width: 850px) { 67 | .box{ 68 | width:100%; 69 | padding: 0; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 30 Days of Web Dev - Liz Krane's April 2015 challenge 4 | --- 5 |

    30 Days of Web Dev: Liz Krane's April 2015 challenge

    6 |

    Update: I'm continuing the project with a daily web dev blog here!

    7 |

    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+.

    8 | 40 | -------------------------------------------------------------------------------- /pygments.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LearningNerd/30DaysOfWebDev/3dda885b569a09ab0dc84958699864b4f6511d3c/pygments.css -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 60px auto; 3 | width: 70%; 4 | max-width: 950px; 5 | } 6 | p, li, label, button, input { 7 | font-size: 1.5em; 8 | line-height: 1.4em; 9 | color: #333; 10 | } 11 | nav ul, footer ul { 12 | font-family:'Helvetica', 'Arial', 'Sans-Serif'; 13 | padding: 0px; 14 | list-style: none; 15 | font-weight: bold; 16 | } 17 | nav ul li, footer ul li { 18 | display: inline; 19 | margin-right: 20px; 20 | font-size:1em; 21 | } 22 | 23 | a { 24 | text-decoration: none; 25 | color: #999; 26 | } 27 | a:hover { 28 | text-decoration: underline; 29 | } 30 | h1 { 31 | font-size: 3em; 32 | font-family:'Helvetica', 'Arial', 'Sans-Serif'; 33 | } 34 | footer { 35 | border-top: 1px solid #d5d5d5; 36 | font-size: .8em; 37 | margin-top:1em; 38 | clear: both; 39 | } 40 | 41 | ul.posts { 42 | margin: 20px auto 40px; 43 | font-size: 1.5em; 44 | } 45 | 46 | ul.posts li { 47 | list-style: none; 48 | } 49 | 50 | ul#days { 51 | list-style: none; 52 | margin: 0 auto; 53 | } 54 | 55 | ul#days li { 56 | display: inline; 57 | float:left; 58 | margin: 5px; 59 | padding:10px; 60 | width:60px; 61 | height:60px; 62 | font-family:'Helvetica', 'Arial', 'Sans-Serif'; 63 | font-size:1.3em; 64 | font-weight:bold; 65 | text-align:center; 66 | background:#eee; 67 | } 68 | ul#days li.done { 69 | background:#ccee99; 70 | } 71 | ul#days li.missed { 72 | background:#e78e8e; 73 | } 74 | ul#days li:hover { 75 | font-size:1.4em; 76 | } 77 | ul#days a:hover li { 78 | background:#eecc99; 79 | } 80 | --------------------------------------------------------------------------------