def add_individual_post_pages (page_dict, post_dict):
      category =post_dict ["category"]
      specific_sidebar_contents = sidebars [category]
      if category == "stories":
        specific_sidebar_contents = '''<a class="sidebar_standalone_link" href="'''+post_permalink(post_dict)+'''/discussion">Author's notes and comments for '''+post_dict["title"]+'''</a>'''+ specific_sidebar_contents
      
      (HTML, head) = post_dict_html(post_dict)
      extras = {}
      if "after_body" in post_dict:
        extras ["after_body"] = post_dict ["after_body"]
      if "blurb" in post_dict:
        extras ["blurb"] = post_dict ["blurb"]
      if "blurb_image" in post_dict:
        extras ["blurb_image"] = post_dict ["blurb_image"]
      utils.make_page (page_dict,
        post_permalink(post_dict),
          title_formatted_title(post_dict)+("" if (category == "") else " ⊂ "+utils.capitalize_string(category))+" ⊂ Eli Dupree's website",
          head, make_blog_page_body(HTML, specific_sidebar_contents), extras
      )
        
      if category == "stories":
        disc_specific_sidebar_contents = '''<a class="sidebar_standalone_link" href="'''+post_permalink(post_dict)+'''">Return to '''+post_dict["title"]+'''</a>'''+sidebars [category]
        
        discussion_post = story_discussion_post(post_dict)
        (HTML, head) = post_dict_html(discussion_post)
        utils.make_page (page_dict,
          post_permalink(discussion_post),
            title_formatted_title(discussion_post)+" ⊂ "+utils.capitalize_string(category)+" ⊂ Eli Dupree's website",
            head, make_blog_page_body(HTML, disc_specific_sidebar_contents)
        )
Esempio n. 2
0
def add_individual_post_pages (page_dict, post_dict):
      category =post_dict ["category"]
      specific_sidebar_contents = sidebars [category]
      if category == "stories":
        specific_sidebar_contents = '''<a class="sidebar_standalone_link" href="'''+post_permalink(post_dict)+'''/discussion">Author's notes and comments for '''+post_dict["title"]+'''</a>'''+ specific_sidebar_contents
      
      (HTML, head) = post_dict_html(post_dict)
      extras = {}
      if "after_body" in post_dict:
        extras ["after_body"] = post_dict ["after_body"]
      if "blurb" in post_dict:
        extras ["blurb"] = post_dict ["blurb"]
      utils.make_page (page_dict,
        post_dict["path_prefix"]+url_formatted_title(post_dict),
          title_formatted_title(post_dict)+("" if (category == "") else " ⊂ "+utils.capitalize_string(category))+" ⊂ Eli Dupree's website",
          head, make_blog_page_body(HTML, specific_sidebar_contents), extras
      )
        
      if category == "stories":
        disc_specific_sidebar_contents = '''<a class="sidebar_standalone_link" href="'''+post_permalink(post_dict)+'''">Return to '''+post_dict["title"]+'''</a>'''+sidebars [category]
        discussion_post = {
          "title": post_dict["title"]+": Discussion",
          "contents": '''<p>If you haven't read <a href="'''+post_permalink(post_dict)+'''">'''+post_dict["title"]+'''</a> yet, you should do that before reading further.</p>'''+(post_dict["authors_notes"] if "authors_notes" in post_dict else "<p>There are no author's notes yet.</p>"),
          "parent_story": post_dict["title"],
          "category": "" # Not treated as a story.
        }
        (HTML, head) = post_dict_html(discussion_post)
        utils.make_page (page_dict,
          post_dict["path_prefix"]+url_formatted_title(post_dict)+'/discussion',
            title_formatted_title(discussion_post)+" ⊂ "+utils.capitalize_string(category)+" ⊂ Eli Dupree's website",
            head, make_blog_page_body(HTML, disc_specific_sidebar_contents)
        )
def add_home_page(page_dict):
  info = {"home":True}
  utils.make_page (page_dict,
    '/index',
      "Eli Dupree's website",
      '<link rel="" href="http://english-1467731550.spampoison.com/" />',
      '''
        <div><img role="presentation" alt="" class="background" src="/media/top-bar-background.png?rr" /></div>
        <div class="home_page_buffer">
          '''+top_bar.home_string(True)+'''
        </div>
        <div class="home_page_outer">
          <div class="home_page_categories">
            '''+top_bar.games_string(False)+'''<!-- Commenting out white space to prevent inline-block issues
            -->'''+top_bar.comics_string(False)+'''<!--
            -->'''+top_bar.stories_string(False)+'''<!--
            -->'''+top_bar.blog_string(False)+'''<!--
            -->'''+top_bar.misc_string(False)+'''
          </div>
          <div class="home_page_bottom">
            <div class="home_page_bottom_inner">
              '''+bottom_bar_contents(info)+'''
            </div>
            <div class="home_page_recent_updates">
              <h2> Recent updates...</h2>
              '''+ blog.recent_updates (9) +'''
            </div>
          </div>
        </div>
      ''',
      {"canonical_path_override": "/", "blurb": "Games – Comics – Stories – Blog", "blurb_image": "/media/site-logo.png?rr"}
  )
def add_page(page_dict):
  utils.make_page (page_dict,
    '/design-generator',
      "Design generator ⊂ Eli Dupree's website",
      r'''<style type="text/css">
html,body {background-color: white;}
    </style> 
    ''',
      '''<a class="skip" href="#content">Skip to content</a>
      '''+bars.bars_wrap({"games":True}, '''<main><div id="content">
      <canvas id="canvas" width="1056" height="816"></canvas>
      </div>
     </main>'''), {"after_body":'''
     <script type="text/javascript" src="/media/paper-full.js?rr"></script>
     <script type="text/paperscript" src="/media/design-generator.js?rr" canvas="canvas"></script>'''}
  )
Esempio n. 5
0
def add_comic_pages(page_dict):
  for comic_id,page_list in comics_pages.items():
    
    archive_entries = ['<div class="comic_archive_chapter">'+
      (
      '<h1>'+comics_metadata[comic_id]["title"]+'</h1>'
      if "archive_promoted" in comics_metadata[comic_id] else
      '<h1>Archive of <span class="title">'+comics_metadata[comic_id]["title"]+'</span></h1>'
      ) + (comics_metadata [comic_id] ["archive_blurb"] if "archive_blurb" in comics_metadata [comic_id] else "")]
    
    for i in range(0,len(page_list)):
      page = page_list[i]
      prev_page = (page_list[i-1] if i>0 else None)
      next_page = (page_list[i+1] if i+1 < len(page_list) else None)
      html, head = page_html_and_head(page, prev_page, next_page)
      extra_scripts = "window.elidupree.handle_content_warnings('"+comic_id+"', true)\n"
      if next_page:
        head = head+'<link rel="next prefetch prerender" href="'+page_url(next_page)+'" />\n<link rel="prefetch" href="'+comic_image_url(next_page)+'" />\n'
        extra_scripts = extra_scripts + "if (document.referrer.indexOf('"+page_url(next_page)+"') !== -1) { document.documentElement.className += ' content_warning_dismissed'; }\n"
      if prev_page:
        head = head+'<link rel="prev prefetch prerender" href="'+page_url(prev_page)+'" />\n<link rel="prefetch" href="'+comic_image_url(prev_page)+'" />\n'
        extra_scripts = extra_scripts + "if (document.referrer.indexOf('"+page_url(prev_page)+"') !== -1) { document.documentElement.className += ' content_warning_dismissed'; }\n"
      utils.make_page (page_dict,
        page_url(page),
          ('Page '+str(page ["page_number"])+' ⊂ ' if i>0 else '')+comics_metadata[comic_id]["title"]+" ⊂ Eli Dupree's website",
          head,
          '<script>'+extra_scripts+'''</script>
  <a class="skip" href="#content">Skip to content</a>'''+bars_wrap({"comics":True}, html, page), {"html_class":comics_metadata[comic_id]["html_class"]}
      )
      
      if "chapter_start" in page:
        archive_entries.append('</div><div class="comic_archive_chapter"><h2>'+page["chapter_start"]+'</h2>')
      archive_entries.append('<a class="comic_archive_entry" href="'+page_url(page)+'"><img class="comic_archive_entry" src="'+comic_image_url(page, 'thumbnail_full')+'" alt="' +'Page '+str(page ["page_number"])+ '"></a>')
    
    extras = {"html_class":comics_metadata[comic_id]["html_class"]}
    if comic_id == "studio_art":
      extras ["blurb"] = studio_art.blurb
      extras ["blurb_image"] = "/media/studio_art_12.png?rr"
    
    archive_entries.append('</div>')
    archive_html = '<main><div id="content" class="comic_archive">'+''.join(archive_entries)+'</div></main>'
    utils.make_page (page_dict,
      comics_metadata[comic_id]["url"]+ ("" if "archive_promoted" in comics_metadata [comic_id] else '/archive'),
        'Archive ⊂ '+comics_metadata[comic_id]["title"]+" ⊂ Eli Dupree's website",
        head,
        '''
<a class="skip" href="#content">Skip to content</a>'''+bars.bars_wrap({"comics":True}, archive_html), extras)
Esempio n. 6
0
def add_secrets (page_dict, orphaned_pages):
  utils.make_page (page_dict,
    '/secrets',
    "Secrets ⊂ Eli Dupree's website",
    "",
      '''<a class="skip" href="#content">Skip to content</a>
      '''+ utils.background_image () +'''
      '''+bars.bars_wrap({"games":True}, '''<main>
  <div id="content">
    '''
    + exhibit (None, "", None, '''<h1> Secrets </h1> <p>Congratulations! You've arrived at the secret page. You probably got here by supporting <a href="https://www.patreon.com/EliDupree">my Patreon page</a>. (Thank you so much!) Or maybe you found it by snooping through <a href="https://github.com/elidupree">my code on GitHub</a>. (You're very clever!) Either way, welcome! Enjoy the secrets below.</p> ''', None)
    + exhibit (None, "", None, '''<p>When I work on designing this website, I use a debug mode that displays some extra information. Right now, the only hidden information is an estimate of the reading difficulty (in US grade level) of each post.</p> <p><a class="debug" id="disable_debug_mode" href=" javascript:;">Disable debug mode</a><a class="not_debug" id="enable_debug_mode" href=" javascript:;">Enable debug mode</a></p> ''', None)
    + exhibit (None, "", None, '''<p>Not all of my posts are accessible from the main page. Sometimes, I create a temporary page for testing purposes, or a game that is still a work-in-progress. Sometimes, I write things to share with specific fandoms, which wouldn't necessarily be interesting to a general audience (or I might want to avoid recommending a show even by mentioning it). You, dear reader, have special access.</p> <p>All currently inaccessible pages are listed below. If you start from this page and follow links, you can reach everything on elidupree.com.</p> <p>'''+ "".join (['''<a class="orphaned_page_link" href="'''+ page +'''">'''+ page +'''</a>''' for page in orphaned_pages]) + "</p> ", None)
        
    +'''<div class="category_page_bottom"></div>
  </div>
</main>'''), {"after_body":'''<script type="text/javascript">
add_event_listener (document.getElementById ("enable_debug_mode"), "click", function () {set_cookie ("debug_mode", true, 30);document.documentElement.className += " debug_mode";});
add_event_listener (document.getElementById ("disable_debug_mode"), "click", function () {delete_cookie ("debug_mode"); remove_class (document.documentElement, "debug_mode");});
</script>'''}
  )
Esempio n. 7
0
def add_list_pages (page_dict, page_list, prefix, title, identifier):
  for page_number in range (0,len( page_list)):
    page = page_list [page_number]
    if page_number ==len( page_list) - 1:
      url_pagenum_string = ''
    else:
      url_pagenum_string = '/page/'+str(page_number + 1)
      
    for page_order in ('','/chronological'):
          end_links = ''
          if page_number > 0:
            end_links = (end_links+'<a href="'+ prefix+'/page/'+str(page_number)+page_order+
              '" rel="prev" class="blog_end_link nav">Older posts</a>')
          if page_number <len( page_list) - 1:
            end_links = (end_links+'<a href="'+ prefix +('/page/'+str(page_number+2)+page_order if (page_number <len( page_list) - 2) else page_order)+
              '" rel="next" class="blog_end_link nav right">Newer posts</a>')
          
          if (page_number >0) or ((page_order != '/chronological') and (len(page) > 1)):
            page_string = ""
            if len (page_list) >1:
              page_string = "/page/1"            
            label = "Read in chronological order"
            
            if page_number > 0:
              label = "Go back to the beginning and read in chronological order"
            end_links = end_links+'''
  <div class="blog_end_links_2">
    <a class="blog_end_link" href="'''+ prefix + page_string +'''/chronological">''' + label + '''</a>
  </div>'''

          entries = [stream_entry (post_dict ) for post_dict in (page if page_order == "/chronological" else reversed(page))]
          if page_order != "/chronological" or len (page_list) >1 or len (page) >1:
            utils.make_page (page_dict,
            prefix +url_pagenum_string+page_order,
              title +" ⊂ Eli Dupree's website",
              "".join ([entry [1] for entry in entries]),
              make_blog_page_body("\n".join([entry [0] for entry in entries]) +end_links, sidebars [identifier])
          )
def add_page(page_dict):
  utils.make_page (page_dict,
    '/microphone-playground',
      "Microphone playground ⊂ Eli Dupree's website",
      r'''<style type="text/css">
html,body {background-color: white;}

    </style> 
    ''',
      '''<a class="skip" href="#content">Skip to content</a>
      '''+bars.bars_wrap({"games":True}, '''
  <main>
    <div id="content"></div>
  </main>
'''), {"blurb": blurb, "blurb_image": blurb_image, "after_body":'''
     
     <script type="text/javascript" src="/media/sound-processing-utils.js?rr"></script>
     
     <script type="text/javascript">
$(function(){
  "use strict";
 
  /* possible risk of things getting garbage collected when they shouldn't be? Stick them in a global */
  window.global_hack = {};
  
  var audio = new (window.AudioContext || window.webkitAudioContext)();
  var rate = audio.sampleRate;
  var recorder_buffer_length = 2048;
  var recorder;
  var memories = [];
  
  var source;
  
  
  
  

  navigator.getUserMedia = (navigator.getUserMedia ||
                            navigator.webkitGetUserMedia ||
                            navigator.mozGetUserMedia ||
                            navigator.msGetUserMedia);
  navigator.getUserMedia ({ audio: true },
    // Success callback
    function(stream) {
      source = audio.createMediaStreamSource(stream);
      recorder = window.global_hack.recorder = audio.createScriptProcessor (recorder_buffer_length, source.numberOfOutputs, 1);
      /* major hack: the recorder is NOT supposed to be connected to anything,
      but in Chrome, it literally doesn't work unless you connect it to the destination. In any case, it only outputs silence, so the workaround is tolerable. */
      recorder.connect (audio.destination);
      source.connect (recorder);
      
      recorder.onaudioprocess = window.global_hack.audio_process = function (event) {
        var input = event.inputBuffer;
        var total = 0;
        for (var channel = 0; channel < input.numberOfChannels; ++channel) {
          var data = input.getChannelData (channel);
          if (memories.length <= channel) {memories.push (annoying.create_memory(audio));}
          var memory = memories [channel];
          for (var sample = 0; sample < data.length; ++sample) {
            annoying.process_sample (memory, data [sample]);
          }
          total += memory.average_excess;
        }
        var average = total/input.numberOfChannels;
        //$("#content").text(Math.round(average*100)).css("text-align", "right");
        $("#content").append($("<div>").css({display:"inline-block", width: 1, height: Math.round(average*100), "background-color": "black"}).text(" "));
      }
    },

    // Error callback
    function(err) {
      console.log('The following gUM error occured: ' + err);
    }
  );
  
});
</script>
'''}
  )
Esempio n. 9
0
def add_game(page_dict):
  utils.make_page (page_dict,
    '/games/green-caves',
      "Enter the green caves ⊂ Eli Dupree's website",
      r'''
    <style type="text/css">
html,body { height: 100%; margin: 0; padding: 0; background-color: #50d050 }
div,canvas { margin: 0; padding: 0; }
canvas {width:804px; height:600px; background-color:#4b4; display: block;}
div.green_caves_content {text-align: center;}
div.green_caves_content .comments_section {display: inline-block;}
p {text-align: center; font-size: 120%;}
    </style>''',
      '''<a class="skip" href="#content">Skip to content</a>
      '''+bars.bars_wrap({"games":True}, '''<main>
      <div class="green_caves_content">
    <div style="min-height: 50%;"><div style="min-height: 390px"></div></div><div style=" margin-top:-390px">
      <p>You are surrounded by endless caves.</p>
      <p>'''+ blurb +'''</p>
      <p>Arrow keys or WASD to move. Point and click to shoot. Click in the <strong>timeline</strong> at the right to return to different points in history!</p>
    </div>
    <div style="margin: 0 auto; width:804px; cursor: crosshair"><canvas id="game_canvas" width="804" height="600">
The game should appear here, but it hasn't. Maybe you don't have JavaScript enabled. Or maybe your browser doesn't support WebGL.
    </canvas></div>
  <p class="hidden_from_restricted_users">If you like this game, consider <a href="https://www.patreon.com/EliDupree">supporting me on Patreon</a> so that I can continue making awesome things and sharing them for free on the Internet.</p>
  <p> The current version of this game is part of the <a href="https://github.com/elidupree/Lasercake">Lasercake project</a>, and it is licensed under the GNU AGPL. It was compiled as of <a href="https://github.com/elidupree/Lasercake/commit/38117543a3f4d864d8544980c317eb311b4b83ba">this commit</a>.</p>
    '''+blog.comments_section("green_caves")+'''

    </div>
    
   
</main>''')+''' <script type="text/javascript">
      Module = {};
      window.green_caves = {};
      window.green_caves.holding_mouse_down_ingame = false;
      Module.TOTAL_MEMORY = 100000000; // 100 MB, about six times the default
      Module.memoryInitializerPrefixURL = "/media/";
      Module.onRuntimeInitialized = function(){
"use strict";

var game_canvas = document.getElementById("game_canvas");

var h = GL.createContext(game_canvas, {});
if (h > 0) {
  window.green_caves.gl = true;
  GL.makeContextCurrent(h);
}
else {
  window.green_caves.gl = false;
  window.green_caves.canvas_context = game_canvas.getContext("2d");
}

/*  (function(window){
    var green_caves = {
      window.green_caves.canvas_context: game_canvas.getContext("2d"),
    };
    window.green_caves = green_caves;
  })(window);*/

  var canvas_rect = game_canvas.getBoundingClientRect();
  function update_canvas_rect() {
    canvas_rect = game_canvas.getBoundingClientRect();
    window._set_display_size(canvas_rect.right - canvas_rect.left, canvas_rect.bottom - canvas_rect.top, window.green_caves.gl);
  }
  update_canvas_rect();
  
  function mouse_xy_relative_to_top_left_of_canvas(mouseevent) {
    // bug?- assumes the event target is 'document'
    var mouse_screen_x = mouseevent.clientX;
    var mouse_screen_y = mouseevent.clientY;
    // compute this every time in case the window resizes/scrolls
    update_canvas_rect();
    var canvas_screen_top  = canvas_rect.top;
    var canvas_screen_left = canvas_rect.left;
    var mouse_canvas_x = mouse_screen_x - canvas_screen_left;
    var mouse_canvas_y = mouse_screen_y - canvas_screen_top;
    return { x: mouse_canvas_x, y: mouse_canvas_y };
  }
  
  var now_milliseconds = (function(){
    var now = new Date();
    return now.getTime();
  });
  var update = (function(){
    window._update_to_real_time(now_milliseconds());
  });

  var mousemove_while_mouse_down_ingame = function(e){
    var pos = mouse_xy_relative_to_top_left_of_canvas(e);
    window._mouse_moves(e.timeStamp, pos.x, pos.y);
    // prevent selecting text while aiming
    e.preventDefault();
  };
  document.addEventListener('mousedown', function(e){
    if(e.button == 0 && e.target == game_canvas) {
      // left-click in game area
      // (allow left-clicks elsewhere to do normal things like selecting text)
      window.green_caves.holding_mouse_down_ingame = true;
      document.addEventListener('mousemove', mousemove_while_mouse_down_ingame, false);
      var pos = mouse_xy_relative_to_top_left_of_canvas(e);
      window._mouse_down(e.timeStamp, pos.x, pos.y);
    }
  }, false);
  document.addEventListener('mouseup', function(e){
    if(e.button == 0 && window.green_caves.holding_mouse_down_ingame) {
      // release left-click
      window.green_caves.holding_mouse_down_ingame = false;
      document.removeEventListener('mousemove', mousemove_while_mouse_down_ingame, false);
      var pos = mouse_xy_relative_to_top_left_of_canvas(e);
      window._mouse_up(e.timeStamp, pos.x, pos.y);
    }
  }, false);
  document.addEventListener('keydown', function(e){
    var keycode = (e.keyCode ? e.keyCode : e.which);
    var keystr = String.fromCharCode(keycode);
    if (keycode === 37 || keystr === "A") { _set_left(e.timeStamp, true); e.preventDefault(); }
    if (keycode === 38 || keystr === "W") { _set_up(e.timeStamp, true); e.preventDefault(); }
    if (keycode === 39 || keystr === "D") { _set_right(e.timeStamp, true); e.preventDefault(); }
    if (keycode === 40 || keystr === "S") { _set_down(e.timeStamp, true); e.preventDefault(); }
  }, false);
  document.addEventListener('keyup', function(e){
    var keycode = (e.keyCode ? e.keyCode : e.which);
    var keystr = String.fromCharCode(keycode);
    if (keycode === 37 || keystr === "A") { _set_left(e.timeStamp, false); e.preventDefault(); }
    if (keycode === 38 || keystr === "W") { _set_up(e.timeStamp, false); e.preventDefault(); }
    if (keycode === 39 || keystr === "D") { _set_right(e.timeStamp, false); e.preventDefault(); }
    if (keycode === 40 || keystr === "S") { _set_down(e.timeStamp, false); e.preventDefault(); }
  }, false);
  
  var draw = (function(){
    update_canvas_rect();
    if (window.green_caves.gl) {
      
    }
    else {
      window.green_caves.canvas_context.fillStyle = "black";
      window.green_caves.canvas_context.fillRect(0,0,640,640);
    }
    window._draw(window.green_caves.gl);
  });
  
  var tick_finished = true;
  (function tickloop(){
    setTimeout(tickloop, 10);
    if (tick_finished) {
      tick_finished = false;
      update();
      draw();
      tick_finished = true;
    }
  }());

};
    </script>
    <script type="text/javascript" src="/media/green_caves.js?rr"></script>''', {"blurb": blurb, "blurb_image": "/media/green-caves-screenshot.png?rr"}
  )
def add_game(page_dict):
  utils.make_page (page_dict,
    '/games/pac-asteroids',
      "Pac-Asteroids ⊂ Eli Dupree's website",
      r'''<style type="text/css">
html,body {background-color: white;}
    </style> 
    <script type="text/javascript">
Math.TAU = Math.PI*2;
$(function(){
  var TILE_SIZE = 20;
  var ARENA_WIDTH = 32;
  var ARENA_HEIGHT = 24;
  var DOT_RADIUS = TILE_SIZE/12;
  var MIN_ASTEROID_RADIUS = DOT_RADIUS * 4;
  var BULLET_SPEED = 6;
  var BULLET_DURATION = (TILE_SIZE * 12 / BULLET_SPEED);
  var PACMAN_RADIUS = TILE_SIZE / 2;

  var game_canvas_context = document.getElementById("game_canvas").getContext("2d");
  var tick_finished = true;
  var ticks = 0, tick_skips = 0;
  var mousedown = false; 
  var you_base_loc = { x: Math.floor(ARENA_WIDTH / 2), y: Math.floor(ARENA_HEIGHT / 2) };
  var mouse_x = 0, mouse_y = 0;
  var wanted_movement = { x: 0, y: 0 };
  var current_movement = { x: 0, y: 0 }, progress = 0;
  var dots = [];
  var asteroids = [];
  var bullets = [];
  var you_have_lost = false;

  var i, j, x, y;

  var draw_all_four = function(real_x, real_y, draw_func){
    var draw_x, draw_y, which_x, which_y;
    for (which_x = 0; which_x < 2; ++which_x) {
      for (which_y = 0; which_y < 2; ++which_y) {
        draw_x = real_x;
        draw_y = real_y;

        if (which_x === 1) {
          if (draw_x > ARENA_WIDTH * TILE_SIZE / 2) { draw_x -= ARENA_WIDTH * TILE_SIZE; }
          else { draw_x += ARENA_WIDTH * TILE_SIZE; }
        }
        if (which_y === 1) {
          if (draw_y > ARENA_HEIGHT * TILE_SIZE / 2) { draw_y -= ARENA_HEIGHT * TILE_SIZE; }
          else { draw_y += ARENA_HEIGHT * TILE_SIZE; }
        }

        draw_func(draw_x, draw_y);
      }
    }
  };

  var tick = function(){
    var you_movement_angle = 0;
    var mouth_open_angle = 0;
    var you_actual_loc;
    var dx, dy, mag;
    var i, j, x, y;
    game_canvas_context.fillStyle = "black";
    game_canvas_context.fillRect(0,0,640,480);
    if (!you_have_lost) {
      progress += (1/8);
      if (progress >= 1 || (current_movement.x === 0 && current_movement.y === 0)) {
        progress = 0;
        you_base_loc.x += current_movement.x;
        you_base_loc.y += current_movement.y;
        if (you_base_loc.x < 0) you_base_loc.x += ARENA_WIDTH;
        if (you_base_loc.y < 0) you_base_loc.y += ARENA_HEIGHT;
        if (you_base_loc.x >= ARENA_WIDTH) you_base_loc.x -= ARENA_WIDTH;
        if (you_base_loc.y >= ARENA_HEIGHT) you_base_loc.y -= ARENA_HEIGHT;
        current_movement.x = wanted_movement.x;
        current_movement.y = wanted_movement.y;
        if (dots[you_base_loc.x][you_base_loc.y]) {
          dots[you_base_loc.x][you_base_loc.y] = false;
          dx = mouse_x - you_base_loc.x*TILE_SIZE;
          dy = mouse_y - you_base_loc.y*TILE_SIZE;
          mag = Math.sqrt(dx*dx + dy*dy);
          if (mag === 0) { dx = 1; dy = 0; mag = 1; }
          bullets.push({ x: you_base_loc.x*TILE_SIZE, y: you_base_loc.y*TILE_SIZE, vx: dx*BULLET_SPEED/mag, vy: dy*BULLET_SPEED/mag, duration: BULLET_DURATION });
        }
      }
    }
    you_actual_loc = { x: TILE_SIZE*(you_base_loc.x + (current_movement.x * progress)),
                       y: TILE_SIZE*(you_base_loc.y + (current_movement.y * progress)) };

    var move_and_wrap = function(thing){
      thing.x += thing.vx;
      thing.y += thing.vy;
      if (thing.x < 0) thing.x += ARENA_WIDTH*TILE_SIZE;
      if (thing.y < 0) thing.y += ARENA_HEIGHT*TILE_SIZE;
      if (thing.x >= ARENA_WIDTH*TILE_SIZE) thing.x -= ARENA_WIDTH*TILE_SIZE;
      if (thing.y >= ARENA_HEIGHT*TILE_SIZE) thing.y -= ARENA_HEIGHT*TILE_SIZE;
    }
    var intersects = function(loc1, rad1, loc2, rad2) {
      var dx, dy;
      dx = Math.abs(loc1.x - loc2.x);
      if (dx > ARENA_WIDTH*TILE_SIZE/2) { dx = ARENA_WIDTH*TILE_SIZE - dx; }
      dy = Math.abs(loc1.y - loc2.y);
      if (dy > ARENA_HEIGHT*TILE_SIZE/2) { dy = ARENA_HEIGHT*TILE_SIZE - dy; }
      return (dx * dx + dy * dy <= (rad1 + rad2) * (rad1 + rad2));
    }
    for (i = 0; i < asteroids.length; i++) {
      move_and_wrap(asteroids[i]);
    }
    for (i = 0; i < bullets.length; i++) {
      bullets[i].duration--;
      if (bullets[i].duration <= 0) {
        bullets.splice(i, 1);
        i--; // so we don't skip the next bullet 
      }
      else {
        move_and_wrap(bullets[i]);
      }
    }
    for (i = 0; i < bullets.length; i++) {
      for (j = 0; j < asteroids.length; j++) {
        if (intersects(bullets[i], DOT_RADIUS, asteroids[j], asteroids[j].radius)) {
          // blargh... this "function" is just to give me a new scope
          (function(){
            var first_vel_dir;
            var vel_mag;
            if (asteroids[j].radius >= MIN_ASTEROID_RADIUS * 2) {
              first_vel_dir = Math.random() * Math.TAU;
              vel_mag = 1 + Math.random()*2;
              asteroids.push({
                radius: asteroids[j].radius / 2,
                x: asteroids[j].x,
                y: asteroids[j].y,
                vx: asteroids[j].vx + Math.cos(first_vel_dir)*vel_mag,
                vy: asteroids[j].vy + Math.sin(first_vel_dir)*vel_mag,
                });
              asteroids.push({
                radius: asteroids[j].radius / 2,
                x: asteroids[j].x,
                y: asteroids[j].y,
                vx: asteroids[j].vx - Math.cos(first_vel_dir)*vel_mag,
                vy: asteroids[j].vy - Math.sin(first_vel_dir)*vel_mag,
                });
            }
          }());
          asteroids.splice(j, 1);
          bullets.splice(i, 1);
          i--; // so we don't skip the next bullet
          break;
        }
      }
    }
    for (i = 0; i < asteroids.length; i++) {
      if (intersects(you_actual_loc, PACMAN_RADIUS, asteroids[i], asteroids[i].radius)) {
        you_have_lost = true;
      }
    }

    game_canvas_context.fillStyle = "yellow";

    if (current_movement.x > 0) you_movement_angle = 0;
    if (current_movement.y > 0) you_movement_angle = Math.TAU / 4;
    if (current_movement.x < 0) you_movement_angle = Math.TAU / 2;
    if (current_movement.y < 0) you_movement_angle = Math.TAU * 3 / 4;
    mouth_open_angle = 0.5 - Math.abs(progress - 0.5);

    draw_all_four(you_actual_loc.x, you_actual_loc.y, function(draw_x, draw_y){
      var EYE_LENGTH = PACMAN_RADIUS / 4;
      var EYE_DISTANCE = PACMAN_RADIUS / 2.8;
      if (you_have_lost) {
        game_canvas_context.beginPath();
        game_canvas_context.arc(draw_x,draw_y,PACMAN_RADIUS,0,Math.TAU,true);
        game_canvas_context.closePath();
        game_canvas_context.fill();

        game_canvas_context.lineWidth = 1.5;
        game_canvas_context.beginPath();
        game_canvas_context.moveTo(draw_x - EYE_DISTANCE - EYE_LENGTH, draw_y - EYE_DISTANCE - EYE_LENGTH);
        game_canvas_context.lineTo(draw_x - EYE_DISTANCE + EYE_LENGTH, draw_y - EYE_DISTANCE + EYE_LENGTH);
        game_canvas_context.moveTo(draw_x - EYE_DISTANCE - EYE_LENGTH, draw_y - EYE_DISTANCE + EYE_LENGTH);
        game_canvas_context.lineTo(draw_x - EYE_DISTANCE + EYE_LENGTH, draw_y - EYE_DISTANCE - EYE_LENGTH);
        game_canvas_context.moveTo(draw_x + EYE_DISTANCE - EYE_LENGTH, draw_y - EYE_DISTANCE - EYE_LENGTH);
        game_canvas_context.lineTo(draw_x + EYE_DISTANCE + EYE_LENGTH, draw_y - EYE_DISTANCE + EYE_LENGTH);
        game_canvas_context.moveTo(draw_x + EYE_DISTANCE - EYE_LENGTH, draw_y - EYE_DISTANCE + EYE_LENGTH);
        game_canvas_context.lineTo(draw_x + EYE_DISTANCE + EYE_LENGTH, draw_y - EYE_DISTANCE - EYE_LENGTH);
        game_canvas_context.stroke();
      }
      else {
        game_canvas_context.beginPath();
        game_canvas_context.arc(draw_x,draw_y,PACMAN_RADIUS,you_movement_angle + mouth_open_angle,you_movement_angle + Math.TAU - mouth_open_angle,false);
        game_canvas_context.lineTo(draw_x,draw_y);
        game_canvas_context.closePath();
        game_canvas_context.fill();
      }
    });
    for (x = 0; x < ARENA_WIDTH; x++) {
      for (y = 0; y < ARENA_WIDTH; y++) {
        if (dots[x][y]) {
          draw_all_four(x*TILE_SIZE, y*TILE_SIZE, function(draw_x, draw_y){
            game_canvas_context.beginPath();
            game_canvas_context.arc(draw_x,draw_y,DOT_RADIUS,0,Math.TAU,true);
            game_canvas_context.closePath();
            game_canvas_context.fill();
          });
        }
      }
    }
    for (i = 0; i < bullets.length; i++) {
      draw_all_four(bullets[i].x, bullets[i].y, function(draw_x, draw_y){
        game_canvas_context.beginPath();
        game_canvas_context.arc(draw_x, draw_y, DOT_RADIUS, 0, Math.TAU, true);
        game_canvas_context.closePath();
        game_canvas_context.fill();
      });
    }
    game_canvas_context.fillStyle = "gray";
    for (i = 0; i < asteroids.length; i++) {
      draw_all_four(asteroids[i].x, asteroids[i].y, function(draw_x, draw_y){
        game_canvas_context.beginPath();
        game_canvas_context.arc(draw_x, draw_y, asteroids[i].radius, 0, Math.TAU, true);
        game_canvas_context.closePath();
        game_canvas_context.fill();
      });
    }

    if (mousedown) {  
      game_canvas_context.beginPath();
      game_canvas_context.arc(mouse_x,mouse_y,15,0,Math.TAU,true);
      game_canvas_context.closePath();
      game_canvas_context.stroke();
    }
  };

  for (x = 0; x < ARENA_WIDTH; x++) {
    dots[x] = [];
    for (y = 0; y < ARENA_HEIGHT; y++) {
      dots[x][y] = true;
    }
  }

  // blargh... this "function" is just to give me a new scope
  (function(){
    var i, loc_rand, vel_dir, vel_mag, x, y;
    for (i = 0; i < 8; ++i) {
      loc_rand = Math.random() * (ARENA_WIDTH + ARENA_HEIGHT) * TILE_SIZE;
      if (loc_rand < ARENA_WIDTH*TILE_SIZE) {
        x = loc_rand; y = 0;
      }
      else {
        x = 0; y = loc_rand - ARENA_WIDTH*TILE_SIZE;
      }
      vel_dir = Math.random() * Math.TAU;
      vel_mag = Math.random() * 3;
      asteroids.push({
        radius: (Math.random() * 1.2 + 0.7) * TILE_SIZE,
        x: x,
        y: y,
        vx: vel_mag * Math.cos(vel_dir),
        vy: vel_mag * Math.sin(vel_dir),
        });
    }
  }());

  $(document).mousedown(function(){
    mousedown = true;
    return false;
  });
  $(document).mouseup(function(){
    mousedown = false;
    return false;
  });
  $(document).mousemove(function(e){
    var offs = $('#game_canvas').offset();
    mouse_x = e.pageX - offs.left;
    mouse_y = e.pageY - offs.top;
    return false;
  });
  $(document).keydown(function(e){
    var keycode = (e.keyCode ? e.keyCode : e.which);
    var keystr = String.fromCharCode(keycode);
    if (keycode === 37 || keystr === "A") { wanted_movement = { x: -1, y: 0 }; }
    if (keycode === 38 || keystr === "W") { wanted_movement = { x: 0, y: -1 }; }
    if (keycode === 39 || keystr === "D") { wanted_movement = { x: 1, y: 0 }; }
    if (keycode === 40 || keystr === "S") { wanted_movement = { x: 0, y: 1 }; }
    return false;
  });



  (function tickloop(){
    setTimeout(tickloop, Math.floor(1000/30));
    if (tick_finished) {
      tick_finished = false;
      tick();
      tick_finished = true;
      ticks += 1;
    }
    else {
      tick_skips += 1;
    }
    $('#info').html('Info:<br/>Control the pac-man with the arrow keys or WASD. Aim with the mouse. Don\'t get hit by the asteroids.<br/>Ticks: '+ticks+'<br/>Ticks skipped: '+tick_skips);
  }());

});
    </script>''',
      '''<a class="skip" href="#content">Skip to content</a>
      '''+bars.bars_wrap({"games":True}, '''<main><div id="content" style=" background-color: white; cursor: crosshair">

    <div style=" width:640px; margin: 0 auto">    <div id="info"></div><canvas id="game_canvas" width="640" height="480">
The game should appear here, but it hasn't. Maybe you don't have JavaScript enabled. Or maybe your browser doesn't support the canvas element.
    </canvas>
    '''+ blog.comments_section ("pac_asteroids") +''' </div>
  </div>
</main>'''), {"jQuery_before": True, "blurb": "A half-baked, unfinished online game.", "blurb_image": "/media/pac-asteroids-thumbnail.png?rr"}
  )
def add_page(page_dict):
  utils.make_page (page_dict,
    '/neural-music-generator',
      "Neural music generator ⊂ Eli Dupree's website",
      r'''<style type="text/css">
html,body {background-color: white;}
    </style> 
    <link rel="stylesheet" href="/media/font-awesome-4.6.3/css/font-awesome.min.css?rr">
    ''',
      '''<a class="skip" href="#content">Skip to content</a>
      '''+bars.bars_wrap({"games":True}, '''<main><div id="content"></div>
     </main>'''), {"blurb": blurb, "blurb_image": blurb_image, "after_body":'''
     <script type="text/javascript" src="/media/audiobuffer-to-wav.js?rr"></script>
     <script type="text/javascript" src="/media/download.js?rr"></script>
     <script type="text/javascript" src="/media/jszip.min.js?rr"></script>
     <script type="text/javascript">

/* possible risk of things getting garbage collected when they shouldn't be? Stick them in a global */
window.global_hack = {}
$(function(){
  "use strict";
  
  var audio = new (window.AudioContext || window.webkitAudioContext)();
  var rate = audio.sampleRate;
  var generator_buffer_length = 2048;
  
  var turn = Math.PI*2;
    
  var source;
  var analyzer = audio.createAnalyser ();
  var generator_node = window.global_hack.generator_node = audio.createScriptProcessor (generator_buffer_length, 1, 1);
  
  var memory = new Float32Array (50);
  var base_matrix = new Float32Array (memory.length*memory.length);
  var current_matrix;
  var mod_locations = new Float32Array (memory.length*memory.length);
  var mod_magnitudes = new Float32Array (memory.length*memory.length);
  
  function duplicate_vector (vector) {
    var result = new Float32Array (vector.length);
    for (var index = 0; index <vector.length;++index) {
      result [index] = vector [index];
    }
    return result
  }
  function randomize (vector) {
    for (var index = 0; index <vector.length;++index) {
      vector [index] = Math.random()*2 - 1;
    }
  }
  function randomize_positive (vector) {
    for (var index = 0; index <vector.length;++index) {
      vector [index] = Math.random();
    }
  }
  randomize (memory);
  randomize (base_matrix);
  randomize_positive (mod_locations);
  randomize (mod_magnitudes);
  current_matrix = duplicate_vector(base_matrix);
  
  function multiply (vector, matrix) {
    var result = new Float32Array (vector.length);
    for (var index = 0; index <vector.length;++index) {
      var whatever = 0;
      for (var output = 0; output <vector.length;++output) {
        whatever += vector [output]*matrix [output + index*vector.length];
      }
      result [index] = Math.tanh (whatever);
    }
    return result;
  }
  
  function draw_matrix (matrix, width, height, canvas, context, scale) {
    canvas.attr ("width", width*scale);
    canvas.attr ("height", height*scale);
    
    for (var index = 0; index <width;++index) {
      for (var output = 0; output <height;++output) {
        var value = matrix [output + index*height];
        var converted = Math.floor ((value + 1)*128);
        context.fillStyle = "rgb(" + converted + "," + converted + "," + converted + ")"
        context.fillRect (index*scale, output*scale, scale, scale);
      }
    }
  }

var top_bar = $(".top_bar");
var bottom_bar = $(".bottom_bar");
var game_element = $("#content");
var game_height;
var game_width;
function update_dimensions() {
  var game_top = top_bar.offset().top + top_bar.height();
  var game_bottom = $(window).height() - bottom_bar.height();
  game_element.height (game_bottom - game_top);
  var width = game_element.width();
  var height = game_element.height();
  game_height = height;
  game_width = width;
}
update_dimensions();
var mouse_X = 0;
var mouse_Y = 0;

function mouse_moved (event) {
  var offset = game_element.offset();
  mouse_X = (event.pageX - offset.left)/game_width;
  mouse_Y = (event.pageY - offset.top)/game_height;
  
  for (var index = 0; index <current_matrix.length;++index) {
    var distance = Math.abs (mod_locations [index] - mouse_X);
    var weight = Math.max (0, 1-distance*10);
    current_matrix [index] = base_matrix [index] + (mouse_Y-0.5)*2*mod_magnitudes [index]*weight;
  }
  draw_matrix (current_matrix, memory.length, memory.length, current_canvas, current_context,3);
  draw_matrix (base_matrix, memory.length, memory.length, base_canvas, base_context,3);
  console.log ("completed");
}
game_element.mousemove (mouse_moved);
game_element.click (function (event) {
  base_matrix = duplicate_vector(current_matrix);
  randomize_positive (mod_locations);
  randomize (mod_magnitudes);
  mouse_moved (event);
});
  var log_min = Math.log (20);
  var log_max = Math.log (3500);
  var log_range = (log_max - log_min);
  var half_log_range = (log_max - log_min)/2;
  var log_very_min = Math.log (1/(4*60));
  var log_very_range = (log_max - log_very_min);
  
  var base_canvas = $("<canvas>");
  var current_canvas = $("<canvas>");
  var base_context = base_canvas[0].getContext ("2d");
  var current_context = current_canvas[0].getContext ("2d");
  game_element.append (base_canvas).append (current_canvas);

  generator_node.connect (audio.destination);
  
  var time = 0;
  function evaluate (node) {
    if (typeof node === 'number') {return node;}
    return node.evaluate (node.parameters);
  }
  function total (parameters) {
    var result = 0;
    parameters.forEach (function (parameter) {
      result += evaluate (parameter);
    });
    return result;
  }
  function product (parameters) {
    var result = 1;
    parameters.forEach (function (parameter) {
      result *= evaluate (parameter);
    });
    return result;
  }
  function average (parameters) {
    var result = 0;
    parameters.forEach (function (parameter) {
      result += evaluate (parameter);
    });
    return result/parameters.length;
  }
  function adjusted_sigmoid(parameters) {
    return 1/(1+ Math.exp (- evaluate (parameters [0])*evaluate (parameters [1])));
  }
  function get_time() {return time;}
  function sin_hack (parameters) {
    return Math.sin (evaluate (parameters [0])*40);
  }
  function sin_frequency (parameters) {
    return Math.sin (time*turn*evaluate (parameters [0])+evaluate (parameters [1]));
  }
  function adjusted_sin (parameters) {
    return Math.sin (
      time*turn*Math.exp (
        evaluate (parameters [0])+
        (evaluate (parameters [1])*evaluate (parameters [2]))
      )
      +evaluate (parameters [3])
    );
  }
  
  function generate_random_node_1 (level) {
    if (level <= 0) {
      if (Math.random() <0.5) {
        return Math.random()*2-1;
      }
      else {
        return {evaluate: get_time};
      }
    }
    if (Math.random() <0.3) {
      return {
        parameters: [generate_random_node_1 (level-1), generate_random_node_1 (level-2)],
        evaluate: total
      };
    }
    if (Math.random() <0.4) {
      return {
        parameters: [generate_random_node_1 (level-1), generate_random_node_1 (level-2)],
        evaluate: product
      };
    }
      return {
        parameters: [generate_random_node_1 (level-1)],
        evaluate: sin_hack
      };
  }
  
  var root = {
          parameters: [generate_random_node_1 (5)],
                  evaluate: sin_hack
                };
  console.log (root);  console.log (evaluate (root));console.log (evaluate (Math.random()*2-1));
 
    
  function generate_random_node_2 (level, ancestor, force_sin) {
    if (level <= 0) {
      if (Math.random() <0.5) {
        return Math.random()*2-1;
      }
      else {
        return {
          parameters: [Math.exp (log_very_min + Math.random()*log_very_range), Math.random()*turn],
          evaluate: sin_frequency
        };
      }
    }
    //function ancestor(level) {
    //  return generate_random_node_2 (Math.floor (Math.random()*level-0.01))
    //}
    if (!force_sin) {
    if (Math.random() <0.3) {
      return {
        parameters: [ancestor(level), ancestor(level)],
        evaluate: average
      };
    }
    if (Math.random() <0.6) {
      return {
        parameters: [ancestor(level), ancestor(level)],
        evaluate: product
      };
    }
    if (Math.random() <0.5) {
      return {
        parameters: [Math.random()*10, ancestor(level)],
        evaluate: adjusted_sigmoid
      };
    }
    }
      return {
        parameters: [log_min+Math.random()*log_range, Math.random()*half_log_range, ancestor(level), Math.random()*turn],
        evaluate: adjusted_sin
      };
  }
  
  function approximate_pitch_hack (node) {
    if (typeof node === 'number') {return 9999999999;}
    if (node.evaluate === sin_frequency) {return node.parameters [0];}
    var result = 999999999;
    node.parameters.forEach (function (parameter) {
      var a = approximate_pitch_hack(parameter);
      result = Math.min (result, Math.abs (a));
      node.parameters.forEach (function (parameter2) {
        var b = approximate_pitch_hack (parameter2);
        var difference = a - b;
        result = Math.min (result, Math.abs (difference));
      });
    });
    if (node.evaluate === adjusted_sin) {return Math.min(1,result)/200;}
    return result;
  }
  
  function random_range (min, max) {
    return min + Math.floor (Math.random()*(max - min));
  }
  
  var depth = 12;
  var surface = 12;
  var components = []
  function ancestor(level) {
  console.log (random_range (0, components.length));
    var first = components [random_range (0, components.length)];
    var second = components [random_range (0, components.length)];
    if (first.evaluate === adjusted_sin) { return ancestor(level); }
    if (second.evaluate === adjusted_sin) { return ancestor(level); }
    var first_hack = approximate_pitch_hack (first);
    var second_hack = approximate_pitch_hack (second);
    //if (first_hack < 1/3500) { return ancestor(level); }
    //if (second_hack < 1/3500) { return ancestor(level); }
    if (first_hack > second_hack || first_hack > 2) { return first; }
    return second;
  }
  for (var index = 0; index <20; ++index) {
    components.push (generate_random_node_2 (0, ancestor));
  }
  for (var index = 0; index <190; ++index) {
    components.push (generate_random_node_2 (1, ancestor));
  }
  for (var index = 0; index <surface ; ++index) {
    components.push (generate_random_node_2 (1, ancestor, true));
  }
  root = {
    parameters: components.slice (-surface), //[generate_random_node_2 (depth),generate_random_node_2 (depth),generate_random_node_2 (depth),generate_random_node_2 (depth)],
    evaluate: average
  };
  console.log (root);  console.log (evaluate (root));console.log (evaluate (Math.random()*2-1));
  
    
  generator_node.onaudioprocess = window.global_hack.audio_process = function (event) {
    update_dimensions();
    var output = event.outputBuffer.getChannelData (0);
    memory = multiply (memory, current_matrix);
    
    
    
    for (var sample = 0; sample <generator_buffer_length;++sample) {
      time += 1/rate;
      output [sample] = evaluate (root);
      //output [sample] = 0;
      //for (var index = 0; index <10;++index) {
      //  var frequency = Math.exp (log_min + (memory [index]+1)*half_log_range);
      //  output [sample] += Math.sin (frequency*turn*sample/rate);
      //}
    }
  }
});

    </script>'''}
  )
Esempio n. 12
0
def add_game(page_dict):
  utils.make_page (page_dict,
    '/games/the-path',
      "The Path ⊂ Eli Dupree's website",
      r'''<style type="text/css">
html,body {background-color: black;}
#game {position: relative;}
.game_canvas {position: absolute; height: 100%; width: 100%;}
.path_component {position: absolute; background-color: white; height: 1px; width: 10%;}
    </style> 
    <script type="text/javascript">
$(function() {
"use strict";

var turn = Math.PI*2;
var game_element = $("#game");
var top_bar = $(".top_bar");
var bottom_bar = $(".bottom_bar");
$(".bars_inner_box").css ("padding-bottom", 0);
//var body = $("body");
//game_element.height (600);
var background_element = $("<canvas>").addClass ("game_canvas")
game_element.append (background_element);
var background_context = background_element[0].getContext ("2d");
var canvas_element = $("<canvas>").addClass ("game_canvas")
game_element.append (canvas_element);
var canvas_context = canvas_element[0].getContext ("2d");

var visible_path_components = 1200;
var seconds_to_travel_visible = 20;
var path_components_per_second = visible_path_components/seconds_to_travel_visible;
var frames_per_second = 60;
var path_components_per_frame = path_components_per_second/frames_per_second;
var player_max_speed = 0.1; // in screens per second
var thing_start_distance = 0.8;

var game_height;
var game_width;
function update_dimensions() {
  var game_top = top_bar.offset().top + top_bar.height();
  var game_bottom = $(window).height() - bottom_bar.height();
  game_element.height (game_bottom - game_top);
  var width = game_element.width();
  var height = game_element.height();
  game_height = height;
  game_width = width;
  if (canvas_element.attr ("width") != width || canvas_element.attr ("height") != height) {
    canvas_element.attr ("width", width).attr ("height", height);
    background_element.attr ("width", width).attr ("height", height);
  }
}
update_dimensions();

var horizon =function() {
    return game_height - game_height/(Math.PI/2);
  };
var flat = {
  height: function (distance) {
    return horizon() + (game_height - horizon()) *(1-distance);
  },
  scale: function (distance) {
    return 1;
  }
}
var cylindrical_fake = {
  height: function (distance) {
    return game_height - Math.sin (distance*(Math.PI/2))*game_height/(Math.PI/2);
  },
  scale: function (distance) {
    return 1 - distance;
  },
}
var linear = function (scale) {return {
  height: function (distance) {
    return horizon() + Math.exp (- scale*distance)*(game_height - horizon());
  },
  scale: function (distance) {
    return Math.exp (- scale*distance);
  },
};};
var hybrid;
hybrid = function (scale) {
  var specific_linear = linear (scale);
  return {
  height: function (distance) {
    return cylindrical_fake.height (distance)*distance + specific_linear.height (distance)*(1 - distance);
  },
  scale: function (distance) {
    return cylindrical_fake.scale (distance)*distance + specific_linear.scale (distance)*(1 - distance);
  },
};};
var cylindrical_real = function (camera_distance, angle_range) {
  function get_coordinates(distance) {
    return [
      camera_distance - Math.sin (angle_range*(1 - distance)),
      1 - Math.cos (angle_range*(1 - distance))
    ];
  }
  function get_camera_distance (coordinates) {
    var Delta_X = coordinates [0];
    return Math.sqrt (Delta_X*Delta_X + coordinates [1]*coordinates [1]);
  }
  function get_drop (coordinates) {
    return Math.atan2 (coordinates [1], coordinates [0]);   
  }
  var distance_factor = get_camera_distance (get_coordinates (0));
  var drop_factor = get_drop (get_coordinates (0));
return {
  height: function (distance) {
    var coordinates = get_coordinates (distance);
    var drop = get_drop (coordinates);
    return horizon() + (game_height - horizon()) *drop/drop_factor;
  },
  scale: function (distance) {
    return distance_factor/get_camera_distance (get_coordinates (distance));
  },
};};

var perspective = cylindrical_real (.11, .1);//hybrid (seconds_to_travel_visible/10);

var default_path = {info: {max_speed: player_max_speed}, data: [{position: 0, velocity: 0, acceleration: 0, element: $("<div/>") .addClass ("path_component")}]};
var player = {kind: "person", position: 0, distance: 0.08, radius: 0.02, speech: []};
var companion = {kind: "person", position: 0, distance: 0.01, radius: 0.025, speech: [], path: default_path,
pronouncements: [
  {text: "Don't stray from the path", delay_from_same: 100, delay_from_any: 5, automatically_at_distance: [0.9,1.1]},
  {text: "It's dangerous out there", delay_from_same: 100, delay_from_any: 5, automatically_at_distance: [2,1000]}
  
]};
var paths = [default_path];
var hills = [];
var skies = [];
var stuff = [];
stuff.push (player); stuff.push (companion);

function draw_position (position, distance) {
  var scale = perspective.scale (distance);
  return game_width*((position - player.position)*scale + 0.5);
}
function draw_at (position, distance) {
  canvas_context.save();
  var scale = perspective.scale (distance);
  var height = perspective.height (distance);
  canvas_context.translate (draw_position (position, distance), height);
  canvas_context.scale (scale, scale);
}

for (var index = 0; index < 15; index++){
  skies.push ({peak: Math.random(), height: Math.random(), steepness: Math.random()*0.1+0.1});
}
function hill_step(draw) {
  var hill_scale = 30;
  if (Math.random() < 1.6/frames_per_second) {
    var hill = {position: player.position + (Math.random ()*2 - 1)*70, height: Math.random()*0.1 + 0.1, radius: Math.random()*0.2 + 0.2, age: 0};
    if ((hill.position-player.position)/hill_scale+ hill.radius <0 || (hill.position-player.position)/hill_scale - hill.radius >0) {
      hills.push (hill);
    }
  }
  
  hills = hills.filter (function (hill) {
    hill.age += 1/frames_per_second;
    if (draw) {
      var peak_height = horizon() - (hill.height * Math.sin ((hill.age/50)*turn/4))*game_height;
      var base_height = peak_height + hill.height*game_height;
      var center = game_width*((hill.position - player.position)/hill_scale + 0.5);
      var radius = game_width*hill.radius;
      
      background_context.beginPath();
      background_context.moveTo(center - radius, base_height);
      background_context.lineTo (center, peak_height);
      background_context.lineTo (center + radius, base_height);
      background_context.fillStyle = "rgb(0, 0, 0)";
      background_context.fill();
    }
    
    return hill.age < 100;
  });
}
for (var index = 0; index < 200*frames_per_second; index++){
  hill_step (false);
}

var mouse_X = 0;
var mouse_Y = 0;
game_element.mousemove (function (event) {
  var offset = game_element.offset();
  mouse_X = event.pageX - offset.left;
  mouse_Y = event.pageY - offset.top;
});

function speech_bubble (text, direction, alpha) {
  canvas_context.save();
  if (direction) {canvas_context.scale (-1, 1);}
  var text_height = Math.min (22, game_width/32);

  canvas_context.font = text_height +"px Arial, Helvetica, sans-serif";
  canvas_context.textBaseline = "middle";
  var text_width = canvas_context.measureText (text).width;
  var padding = Math.max (text_height/2, text_width/13);
  var text_middle = -16-padding - text_height/2;
  var text_top = text_middle-text_height/2;
  var text_bottom = text_middle+text_height/2;
  canvas_context.beginPath();
  canvas_context.moveTo (0,0);
  canvas_context.quadraticCurveTo (17,-5,17, text_bottom + padding);
  canvas_context.quadraticCurveTo (-padding, text_bottom + padding,-padding, text_middle);
  canvas_context.quadraticCurveTo (-padding,text_top - padding, text_width/2, text_top - padding);
  canvas_context.quadraticCurveTo (text_width+padding, text_top - padding, text_width + padding, text_middle);
  canvas_context.quadraticCurveTo (text_width+padding, text_bottom + padding, 30, text_bottom + padding);
  canvas_context.quadraticCurveTo (30,-5,0,0);
  canvas_context.closePath();
  canvas_context.fillStyle = "rgba(255, 255, 255,"+ alpha +")";
  canvas_context.fill();
  canvas_context.strokeStyle = "rgba(0,0,0,"+ alpha +")";
  canvas_context.stroke();
  canvas_context.fillStyle = "rgba(0, 0, 0,"+ alpha +")";
  
  if (direction) {
    canvas_context.scale (-1, 1);
    canvas_context.translate (-text_width, 0);
  }
  canvas_context.fillText (text, 0, text_middle);
  canvas_context.restore();
}

function polygon (points, fill, stroke) {
  canvas_context.beginPath();
  canvas_context.moveTo(points [0], points [1]);
  for (var index = 2; index <points.length; index += 2){
    canvas_context.lineTo(points [index], points [index + 1]);
  }
  close_shape(fill, stroke);
}

function generic_polygon (points) {
  canvas_context.beginPath();
  canvas_context.moveTo(points [0], points [1]);
  for (var index = 2; index <points.length; index += 2){
    canvas_context.lineTo(points [index], points [index + 1]);
  }
  close_generic_shape();
}

function close_shape (fill, stroke) {
  canvas_context.closePath();
  if (fill) {canvas_context.fillStyle = fill;
  canvas_context.fill();}
  if (stroke) {canvas_context.strokeStyle = stroke;
  canvas_context.stroke();}
}


function close_generic_shape () {
  close_shape ("rgb(255, 255, 255)", "rgb(0,0,0)");
}


function draw_person (person) {
  //draw_at (person.position, person.distance);
  //var center = game_width*((person.position - player.position)*perspective.scale (person.distance) + 0.5);
  var center = 0;
  var radius = game_width*person.radius;
  var body_height = - radius*2/3;
  var leg_height = - radius;
  var offset = Math.sin (Date.now()*turn/900)*radius/4;
  if (person.falling_down) {canvas_context.rotate (turn/4);}
  generic_polygon ([
    center - radius/8, leg_height - offset,
    center - radius*2/3, leg_height - offset,
    center - radius*11/24, leg_height - offset + radius
  ]);
  generic_polygon ([
    center + radius/8, leg_height + offset,
    center + radius*2/3, leg_height + offset,
    center + radius*11/24, leg_height + offset + radius
  ]);
  generic_polygon ([
    center - radius, body_height,
    center + radius, body_height,
    center, body_height - 2*radius
  ]);
  canvas_context.beginPath();
  canvas_context.arc (center, body_height - 1.7*radius, radius*0.7, 0, turn, true);
  close_generic_shape();
  //canvas_context.restore();
}

function draw_speech (person) {
  var scale = perspective.scale (person.distance);
  var reference_position = draw_position (person.position, person.distance);
  var radius = game_width*person.radius*scale;
  
  person.speech = person.speech.filter (function(speech) {
    speech.age += 1/frames_per_second;
    if (speech.age >= 3.5) {return false;}
    
    if (speech.age >1.0 && speech.response) {
      speech.response.age = 0;
      speech.response.person.speech.push (speech.response);
      speech.response = undefined;
    }
    
    var distortion = 0;
    if (speech.age < 0.25) {distortion = (0.25 - speech.age)*4;}
    if (speech.age > 3.25) {distortion = (3.25 - speech.age)*4;}
    
    if (reference_position <game_width/3) {speech.direction = false;}
    if (reference_position >game_width*2/3) {speech.direction = true;}
    var speech_position = reference_position + (speech.direction && -1 || 1)* radius;
    if (!speech.direction) {speech_position = Math.max (speech_position, 5);}
    if (speech.direction) {speech_position = Math.min (speech_position, game_width - 5);}
    
    canvas_context.save();
    canvas_context.translate(speech_position, perspective.height (person.distance) - radius*2/3 - 1.7*radius);
    canvas_context.rotate (distortion*turn/17);
    speech_bubble (speech.text, speech.direction, 1.0 - Math.abs (distortion));
    canvas_context.restore();
    return true;
  });
}

function draw_tree (thing) {
  var center = 0;
  var radius = game_width*thing.radius;

  var tree_color = "rgb(70, 70, 70)"
  polygon ([
    center - radius*0.3, 0,
    center + radius*0.3, 0,
    center, - 2*radius
  ],tree_color);
  polygon ([
    center - radius, - radius,
    center + radius, - radius,
    center, - 2.8*radius
  ],tree_color);
  polygon ([
    center - radius*0.8, - 2*radius,
    center + radius*0.8, - 2*radius,
    center, - 3.5*radius
  ],tree_color);
}
function draw_thing (thing) {
  draw_at (thing.position, thing.distance);
  canvas_context.globalAlpha = (thing_start_distance - thing.distance)/0.2;
  
  if (thing.kind == "tree") {draw_tree (thing);}
  if (thing.kind == "person") {draw_person (thing);}
  if (thing.kind == "reward") {
    var points = [];
    var radius = game_width*thing.radius;
    var progress = thing.receiving || 0;
    var offset = - (radius*(1 + 2*progress));
    canvas_context.globalAlpha = Math.min (1, 10 - progress*10);
    for (var index = 0; index <5;++index) {
      points.push (radius*Math.sin (turn*(progress + index/5)));
      points.push (offset - radius*Math.cos (turn*(progress + index/5)));
      points.push (radius*Math.sin (turn*(progress + 0.1 + index/5))/Math.sqrt (5));
      points.push (offset - radius*Math.cos (turn*(progress + 0.1 + index/5))/Math.sqrt (5));
    }
    generic_polygon (points);
  }
  if (thing.kind == "box") {
    var radius = game_width*thing.radius;
    var progress = thing.receiving || 0;
    canvas_context.globalAlpha = Math.min (1, 1 - progress*1);
    canvas_context.beginPath();
    canvas_context.rect (- radius, - radius*1.6, radius*2, radius*1.6);
    canvas_context.fillStyle = "rgb(255, 255, 255)";
    canvas_context.fill();
    canvas_context.strokeStyle = "rgb(0, 0, 0)";
    canvas_context.stroke();

  canvas_context.font = 24+"px Arial, Helvetica, sans-serif";
  canvas_context.textBaseline = "middle";
  canvas_context.textAlign = "center";
  canvas_context.translate (0, - radius*0.8);
  canvas_context.scale (radius/24, radius/24);
  canvas_context.fillStyle = "rgb(0, 0, 0)";
  canvas_context.fillText ("?", 0, 0);

  }
  if (thing.kind == "monster") {
    var radius = game_width*thing.radius;
    var progress = thing.receiving || 0;
    generic_polygon ([- radius*0.8, 0, radius*0.8, 0, 0, - radius*1.6]);
    for (var index = 0; index <3;++index) {
      for (var direction= -1; direction<=1;direction+=2) {
        var height = (0.2 + index/3);
        var tips = (thing.attacking !== undefined) && (1-thing.attacking*1.5) || 1;
        canvas_context.beginPath();
        canvas_context.moveTo (radius*direction/3, - radius*(height+0.8));
        canvas_context.quadraticCurveTo (radius*direction, - radius*(height+0.6), radius*direction*tips, - radius*height);
        canvas_context.quadraticCurveTo (radius*direction*0.84, - radius*(height+0.4), radius*direction/3, - radius*(height+0.2));
        close_generic_shape();
      }
    }
  }

  
  canvas_context.restore();
}

function closest_component (path, distance) {
  return path.data [Math.floor (distance*visible_path_components)];
}
var path_radius = 0.12;
function normalized_distance_from (path, person) {
  return Math.abs (person.position - closest_component (path, person.distance).position)/path_radius;
}

var start = Date.now();
var step = 0;
var pause_next_frame = false;
var permanent_pain = 0.4;
var temporary_pain = 0.4;
var transient_pain = 0.4;
function tick() {
  requestAnimationFrame (tick);
  if (Math.random() <0.2) {return;}
  step++;
  var updated = update_dimensions();
  var width = game_width;
  var height = game_height;
  var time = step/frames_per_second;//(Date.now() - start)/1000;
  var draw_background = updated || (step % Math.floor (frames_per_second/20)) == 1;
  
  var moving = !pause_next_frame;
  
  canvas_context.clearRect (0, 0, width, height);

  
  if (draw_background) {
    background_context.fillStyle = "rgb(0,0,0)";
    background_context.fillRect (0, 0, width, height);
  }
  skies.forEach (function(sky) {
    sky.peak += ((Math.random()*2) - 1)*0.05/frames_per_second;
    sky.height += ((Math.random()*2) - 1)*0.05/frames_per_second;
    sky.peak -= (sky.peak - 0.5)*0.0006/frames_per_second;
    sky.height -= (sky.height - 0.7)*0.0003/frames_per_second;
    if (draw_background) {
    background_context.beginPath();
    var peak = sky.peak*width;
    var sky_height = sky.height*horizon();
    background_context.moveTo(peak - width, sky_height + height*sky.steepness);
    background_context.bezierCurveTo(
      peak - width*0.6,
      sky_height + height*sky.steepness,
      peak - width*0.4,
      sky_height,
      peak,
      sky_height
    );
    background_context.bezierCurveTo(
      peak + width*0.4,
      sky_height,
      peak + width*0.6,
      sky_height + height*sky.steepness,
      peak + width,
      sky_height + height*sky.steepness
    );
    var limit = Math.max (sky_height + height*sky.steepness, horizon());
    background_context.lineTo (width, limit);
    background_context.lineTo (0, limit);
    background_context.fillStyle = "rgba(255, 255, 255, 0.04)";
    background_context.fill();
    }
  });
  
  if (draw_background) {
    background_context.fillStyle = "rgb(0,0,0)";
    background_context.fillRect (0, horizon(), width, height - horizon());
  }
  
  hill_step (draw_background);
        
  paths.forEach (function(path) {
    if (moving) {while (path.data.length <visible_path_components+path_components_per_frame) {
      var previous = path.data [path.data.length - 1];
      var current = {
        position: previous.position + previous.velocity/path_components_per_second,
        velocity: previous.velocity + previous.acceleration/path_components_per_second,
      };
      var max_speed =path.info.max_speed;
      
      var default_change_radius = max_speed*2.16/path_components_per_second;
      var bias = -previous.velocity*0.36/path_components_per_second;
      // the path secretly follows the player if the player moves too far away,
      // for both gameplay and symbolism reasons
      if (player.position - previous.position > 0.7) {
        bias += (player.position - previous.position - 0.7)*0.04/path_components_per_second;
      }
      if (player.position - previous.position < -0.7) {
        bias += (player.position - previous.position + 0.7)*0.04/path_components_per_second;
      }
      var max_acceleration = Math.min (previous.acceleration + default_change_radius + bias, (max_speed - previous.velocity)*20);
      var min_acceleration = Math.max (previous.acceleration - default_change_radius + bias, (-max_speed - previous.velocity)*20);
      current.acceleration = min_acceleration +Math.random()*(max_acceleration - min_acceleration);
      
      
      // What jerk do we need to stop velocity from
      //
      // the maximum acceleration makes the (velocity, time) curve a parabola that touches the maximum velocity line and has its time^2 coefficient based on the minimum jerk (which is negative).
      // I.e. velocity = acceleration*time + (minimum jerk/2)*time ^2, solve for acceleration
      // given that velocity = previous.velocity when acceleration = previous.acceleration
      //
      // acceleration = previous.velocity/time - (minimum jerk/2)*time
      //
      // previous.velocity = previous.acceleration*time + (minimum jerk/2)*time ^2
      // (minimum jerk/2)*time ^2 + previous.acceleration*time - previous.velocity = 0
      // time =
      
            
      //if (current.velocity >max_speed){current.velocity = max_speed;}
      //if (current.velocity <-max_speed){current.velocity = -max_speed;}
      //current.element = $("<div/>") .addClass ("path_component");
      //game_element.append (current.element);
      path.data.push (current);
    }
    //console.log (path.data [0]);
    
    //TODO: avoid rounding error if we adjust the values
    var deleted;
    for (var i=0;i<path_components_per_frame;++i){
      deleted = path.data.shift();
      //deleted.element.detach();
    }
    companion.position = closest_component (path, companion.distance).position;
    }
    
    // you can't get TOO far away from the paths.
    // TODO: possibly better symbolism and gameplay if the paths stay near YOU instead
    //if (player.position - deleted.position > 0.5) {
    //  player.position -= (player.position - deleted.position - 0.5)*3/600/paths.length;
    //}
    //if (player.position - deleted.position < -0.5) {
    //  player.position -= (player.position - deleted.position + 0.5)*3/600/paths.length;
    //}
    
    var component_width = function (index) {
      return width*path_radius*2*perspective.scale (index/visible_path_components); // * Math.sqrt ((1 + Math.abs (current.velocity)*width/(height/visible_path_components)));
    };
    
    canvas_context.fillStyle = "rgb(255, 255, 255)";
    canvas_context.beginPath();
    var began = false;
    var center_X = function (component, index) {return width*((component.position - player.position)*perspective.scale (index/visible_path_components)+ 0.5);};
    path.data.forEach (function(current, index) {
     
      //current.element.css ("bottom", index).css ("left", game_element.width()*(current.position - deleted.position +0.45));
      
      if (began) {
        canvas_context.lineTo(center_X (current, index) -component_width (index)/2, perspective.height (index/visible_path_components));
      }
      else {
        canvas_context.moveTo(center_X (current, index) -component_width (index)/2, perspective.height (index/visible_path_components));
        began = true;
      }
      
    });
    for (var index = path.data.length - 1; index >= 0; index -= 1){
      var current = path.data [index];
      canvas_context.lineTo(center_X (current, index) +component_width (index)/2, perspective.height (index/visible_path_components));
    }
    canvas_context.fill();
  });
  
  
  if (moving && Math.random() < 16/frames_per_second) {
    var thing = {kind: "tree", distance: thing_start_distance, position: player.position + ((Math.random()*2) - 1)*20, radius: 0.05};
    if (Math.random() <0.1) {thing.kind = "monster"; thing.radius = 0.05;}
    if (Math.random() <0.05) {thing.kind = "reward"; thing.radius = 0.03;}
    if (Math.random() <0.1) {thing.kind = "box"; thing.radius = 0.03;}
    stuff.push (thing);
  }
  
  //var boxes = {}
  var collision;
  
  stuff = stuff.filter (function (thing) {
    if (moving && thing.kind != "person") {
      thing.distance -= 1/seconds_to_travel_visible/frames_per_second;
      if (thing.kind == "monster") {
        thing.position += (((Math.random()*2) - 1)*0.1 + (player.position - thing.position)*0.03)/frames_per_second;
      }
    }
    if (thing.distance < -0.3) {return false;}
    if (thing.distance >player.distance && thing.distance <= player.distance + 2/seconds_to_travel_visible/frames_per_second && Math.abs (thing.position - player.position) <thing.radius + player.radius &&!(collision && collision.distance < thing.distance)) {
      collision = thing;
    }
    return true;
  });
  
  stuff.sort (function (first, second) {return second.distance - first.distance;});
  
  stuff.forEach (function (thing) {
    draw_thing (thing);
  });
  
  if (moving) {
    var player_velocity_request = Math.max (-1, Math.min (1, ((mouse_X/width) - 0.5)*10));
    player.position += player_velocity_request*player_max_speed/frames_per_second;
  }
  
  var distance = normalized_distance_from (companion.path, player);
  
  if (collision) {
    pause_next_frame = true;
    if (collision.kind == "tree") {
      if (collision.position >player.position) {player.position -= 0.025/frames_per_second;}
      else {player.position += 0.025/frames_per_second;}
      if (!player.falling_down) {
        temporary_pain += 0.15;
        player.speech.push ({
          text: "Ow, it hurts",
          age: 0,
          response: {
            person: companion,
            text: (distance <= 1.2) && "That's just part of life" || "It's your fault for straying"
          }
        });
      }
      player.falling_down = true;
    }
    if (collision.kind == "reward") {
      if (moving) {
        permanent_pain -= 0.02;
        temporary_pain -= 0.03;
        player.speech.push ({
          text: "Yay!",
          age: 0,
          response: {
            person: companion,
            text: (distance <= 1.2) && "I'm proud of you" || "That's not good for you"
          }
        });
      }
      collision.receiving = (collision.receiving || 0) + 0.7/frames_per_second;
      if (collision.receiving >1) {
        //hack: destroy
        collision.distance = - 1;
      }
    }
    if (collision.kind == "box") {
      if (moving) {
        player.speech.push ({
          text: "What's inside?",
          age: 0,
        });
        var thing = {kind: "reward", distance: collision.distance + 0.001, position: collision.position, radius: 0.03};
        if (Math.random() < 0.24) {
          thing.kind = "monster"; thing.radius = 0.05;
        }
        stuff.push (thing);
      }
      collision.receiving = (collision.receiving || 0) + 1.5/frames_per_second;
      if (collision.receiving >1) {
        //hack: destroy
        collision.distance = - 1;
      }
    }
    if (collision.kind == "monster") {
      if (moving) {
        permanent_pain += 0.02;
        temporary_pain += 0.3;
        player.speech.push ({
          text: "Ow, it hurts!",
          age: 0,
          response: {
            person: companion,
            text: (distance <= 1.2) && "Liar, that would never happen on the path" || "It's your fault for straying"
          }
        });
      }
      collision.attacking = (collision.attacking || 0) + 2/frames_per_second;
      if (collision.attacking>1) {
        // wander past the player without disappearing
        collision.distance = player.distance - 0.001;
      }
    }

  }
  else {
    player.falling_down = false;
    pause_next_frame = false;
  }
  
  temporary_pain += (permanent_pain - temporary_pain)/(2*frames_per_second);
  transient_pain += (temporary_pain - transient_pain)/(frames_per_second/20);

  companion.pronouncements.forEach (function (pronouncement) {
    if (companion.last_pronouncement && companion.last_pronouncement + pronouncement.delay_from_any >time) {return;}
    if (pronouncement.last_spoken && pronouncement.last_spoken + pronouncement.delay_from_same>time) {return;}
    if (pronouncement.automatically_at_distance) {
      if (pronouncement.automatically_at_distance [0] <= distance && pronouncement.automatically_at_distance [1] >= distance) {
        companion.last_pronouncement = time;
        pronouncement.last_spoken = time;
        var direction = companion.position <player.position;
        if (Math.abs (companion.position - player.position) >0.3) {direction =!direction;}
        companion.speech.push ({text: pronouncement.text, direction: direction, age: 0});
      }
    }
  });
  
  canvas_context.save();
  canvas_context.beginPath();
  //canvas_context.moveTo()
  canvas_context.rect (0, 0, width, height);
  canvas_context.scale (width, height);
  canvas_context.arc(0.5, 0.5, (1-transient_pain)/Math.sqrt (2), 0, turn, true);
  //canvas_context.globalCompositeOperation = "destination-in";
  canvas_context.fillStyle = "rgb(0,0,0)";
  canvas_context.fill();
  canvas_context.restore();
  
  draw_speech (player);
  draw_speech (companion);

  
}
tick();


});
    </script>''',
      '''<a class="skip" href="#content">Skip to content</a>
      '''+bars.bars_wrap({"games":True}, '''<main><div id="content">
      
      <div id="game"></div>

    '''+
    # blog.comments_section ("the_path") +
    '''
  </div>
</main>'''), {
  "jQuery_before": True,
  "blurb": "A unfinished online game.",
  #"blurb_image": "/media/pac-asteroids-thumbnail.png?rr"
}
  )
Esempio n. 13
0
def add_page(page_dict):
  utils.make_page (page_dict,
    '/voice-practice-tool',
      "Voice practice tool ⊂ Eli Dupree's website",
      r'''<style type="text/css">
html,body {background-color: white;}
div.recording {display: inline-block; margin:3px;/* padding-left:12px;*/ background-color:#ccc;}
canvas.recording {display: block; cursor: pointer;}
#recent_magnitudes {display: block; cursor: pointer;}
.control_panels {display: inline-block; vertical-align: middle;}
.control_panel {display: inline-block; background-color: transparent; margin:3px; vertical-align: top;}
.control {display: inline-block; background-color:#ccc; color:#555; font-weight: bold; padding:4px; vertical-align: top; cursor: pointer;}
.control.selected {background-color:#5f5; color:#000;}
.text-danger {color:#d9534f;}
.recent_box {float: right; background-color:#ccc;}
.recent_magnitudes_caption {padding:4px;}
.recording_button {padding:0 3px; cursor: pointer;}
.recordings {
    display: flex;
    flex-wrap: wrap-reverse;
}
.page_description {padding:0.3em 2em; font-size: 110%;}

.fa-stack.force_small {width:1em; height:1em; line-height:0.7em;}
span.no_underlay {position: absolute; top: 0; left: 0; z-index: 1;}
    </style> 
    <link rel="stylesheet" href="/media/font-awesome-4.6.3/css/font-awesome.min.css?rr">
    ''',
      '''<a class="skip" href="#content">Skip to content</a>
      '''+bars.bars_wrap({"games":True}, '''
  <main>
    <canvas id="histogram_canvas" width="320" height="80">
The histogram should appear here, but it hasn't. Maybe you don't have JavaScript enabled. Or maybe your browser doesn't support the canvas element.
    </canvas>
    <div class="page_description "></div>
    <div class="control_panels "></div>
    <div class="recent_box">
      <div class="recent_magnitudes_caption "></div>
      <canvas id="recent_magnitudes"></canvas>
    </div>
    <div class="recordings "></div>
  </main>
'''), {"blurb": blurb, "blurb_image": blurb_image, "after_body":'''
     <script type="text/javascript" src="/media/audiobuffer-to-wav.js?rr"></script>
     <script type="text/javascript" src="/media/download.js?rr"></script>
     <script type="text/javascript" src="/media/jszip.min.js?rr"></script>
     
     <!--<script type="text/javascript" src="/media/complex.js?rr"></script>
     <script type="text/javascript" src="/media/pitch.js?rr"></script>-->
     <script type="text/javascript" src="/media/pitchdetect.js?rr"></script>
     
     <script type="text/javascript" src="/media/voice-practice-tool-lib.js?rr"></script>
     
     <script type="text/javascript">
$(function(){
  initialize_voice_practice_tool ({
    page_description: "This page lets you '''+ do_stuff +''' Tested in Firefox and Chrome; may not work in other browsers.",
    recording_created: function (recording) {
      $(".recordings").append (recording.element);
    },
    sizes: function() {
      var width = $("body").width();
      var height = $("body").height();
      return {
        recent_magnitudes_width: Math.ceil (Math.min (width/3, 200)),  
        recent_magnitudes_height: Math.min (height/3, 200),
        histogram_width: width,
        histogram_height: Math.min (height/4, 128),
      }
    },
    controls_next_to_recent: true,
  });
});
</script>
'''}
  )
Esempio n. 14
0
def add_category_pages(page_dict):  
  utils.make_page (page_dict,
    '/comics',
      "Comics ⊂ Eli Dupree's website",
      '',
      '''<a class="skip" href="#content">Skip to content</a>
      '''+ utils.background_image () +'''
      '''+bars.bars_wrap({"comics":True}, '''<main>
  <div id="content">
    '''+ exhibit ("/voldemorts-children", "vc left", "/media/VC_0.png?rr",'''
        <p>What if Dumbledore's idea of placing Harry Potter with an abusive family didn't turn out so well?</p>
        <p><span class="title">Voldemort's Children</span>, my ongoing Harry Potter fanfic graphic novel, explores possible answers. It's about 80% complete, but is on hiatus while my hands recover from an injury.</p>'''+ content_warning_summary ("<p>Content warnings: This comic depicts verbal and physical abuse; physical violence, with occasional cartoon blood and gore; ableist language; negative self-talk; some discussion of suicide.</p><p>As you read the comic, each page will be marked with the warnings that apply to that page.</p>"), "Start reading") +
        exhibit ("/a-couple-of-badass-superheroes" , "acobs right", "/media/ACOBS_thumbnail.png?rr",'''
        <p><span class="title">A Couple of Badass Superheroes</span> go on a silly adventure. I wrote the first part September-December 2011 to get used to drawing using my tablet. (14 pages)</p>''', "Start reading") +
        exhibit ("/people-are-wrong-sometimes", "paws left", "/media/PAWS_thumbnail.png?rr",'''
        <p>In <span class="title">People Are Wrong Sometimes</span>, two friends are about to leave high school and part ways. But do they really know each other? (10 pages)</p>'''+ content_warning_summary ("<p>Content warnings: This comic depicts neurelitist statements; brief descriptions of bullying and violence; one instance of cartoon blood.</p>"), "Start reading") +
'''
    <div class="category_page_bottom"></div>
  </div>
</main>''')

  )
  

  utils.make_page (page_dict,
    '/games',
      "Games ⊂ Eli Dupree's website",
      '',
      '''<a class="skip" href="#content">Skip to content</a>
      '''+ utils.background_image () +'''
      '''+bars.bars_wrap({"games":True}, '''<main>
  <div id="content">
    '''+ exhibit ("http://lasercake.net/", "generic left", "/media/lasercake-snapshot-progressive.jpg?rr", '''Lasercake, an (early prototype of an) open-world game about the environment.''', "Go to website")
    + exhibit ("/games/green-caves", "generic right", "/media/green-caves-screenshot.png?rr", '''A simple online game where you fly around in some green caves.''', "Play now")
    + exhibit ("/hexy", "generic left", "/media/hexy-thumbnail.png?rr", '''Hexy Bondage, a (printable) sexual board game for two or more players.''', "Go to website")
    + exhibit ("/games/pac-asteroids", "generic right", "/media/pac-asteroids-thumbnail.png?rr",'''Pac-asteroids, a half-baked unfinished online game I wrote while learning JavaScript.''', "Play now")
    +'''<div class="category_page_bottom"></div>
  </div>
</main>''')
  )
  
  
  import voice_practice_tool
  utils.make_page (page_dict,
    '/misc',
      "Miscellaneous things ⊂ Eli Dupree's website",
      '',
      '''<a class="skip" href="#content">Skip to content</a>
      '''+ utils.background_image () +'''
      '''+bars.bars_wrap({"games":True}, '''<main>
  <div id="content">
    '''+ exhibit ("/voice-practice-tool", "generic left", voice_practice_tool.blurb_image, voice_practice_tool.blurb, "Use it now") +'''
    
    '''+ exhibit ("/ap-studio-art", "generic right", "/media/studio_art_12.png?rr",'''
        <p>'''+ studio_art.blurb +'''</p>''', "View the gallery") + '''

    <div class="category_page_bottom"></div>
  </div>
</main>''')
  )


  utils.make_page (page_dict,
    '/stories',
      "Stories ⊂ Eli Dupree's website",
      '',
      '''<a class="skip" href="#content">Skip to content</a>
      '''+ utils.background_image () +'''
      '''+bars.bars_wrap({"stories":True}, '''<main>
  <div id="content">'''+ blog_posts.stories_index (True) +'''
    <div class="category_page_bottom"></div>
  </div>
</main>''')
  )
def add_game(page_dict):
  utils.make_page (page_dict,
    '/games/community-simulator',
      "Community simulator ⊂ Eli Dupree's website",
      r'''<style type="text/css">
html,body {background-color: black;}
#game {position: relative;}
.game_canvas {position: absolute; height: 100%; width: 100%;}
.path_component {position: absolute; background-color: white; height: 1px; width: 10%;}
    </style> 
    <script type="text/javascript">
$(function() {
"use strict";

var turn = Math.PI*2;
var game_element = $("#game");
var top_bar = $(".top_bar");
var bottom_bar = $(".bottom_bar");
$(".bars_inner_box").css ("padding-bottom", 0);
//var body = $("body");
//game_element.height (600);

var canvas_element = $("<canvas>").addClass ("game_canvas")
game_element.append (canvas_element);
var canvas_context = canvas_element[0].getContext ("2d");

var frames_per_second = 60;


var game_height;
var game_width;
function update_dimensions() {
  var game_top = top_bar.offset().top + top_bar.height();
  var game_bottom = $(window).height() - bottom_bar.height();
  game_element.height (game_bottom - game_top);
  var width = game_element.width();
  var height = game_element.height();
  game_height = height;
  game_width = width;
  if (canvas_element.attr ("width") != width || canvas_element.attr ("height") != height) {
    canvas_element.attr ("width", width).attr ("height", height);
  }
}
update_dimensions();



var mouse_X = 0;
var mouse_Y = 0;
game_element.mousemove (function (event) {
  var offset = game_element.offset();
  mouse_X = event.pageX - offset.left;
  mouse_Y = event.pageY - offset.top;
});


function close_shape (fill, stroke) {
  canvas_context.closePath();
  if (fill) {canvas_context.fillStyle = fill;
  canvas_context.fill();}
  if (stroke) {canvas_context.strokeStyle = stroke;
  canvas_context.stroke();}
}



var resource_names = ["energy", "companionship"];
var default_speed = 0.1;
var person_radius = 0.04;
var interaction_distance = person_radius*2.2;
//var comfort_distance = person_radius*1.6;
var personal_space_distance = person_radius*1.1;
var resource_decay = 1/10;
var indifference_threshold = 0.01;




var people = [];
for (var index = 0; index <20;++index) {
  var resources = {};
  resource_names.forEach(resource => {
    resources [resource] = {immediate: Math.random(),};
  });
  people.push ({
    index: index,
    x: Math.random(),
    y: Math.random(),
    resources,
    relationships: {},
  });
}
people.forEach(function(person) {
  people.forEach(function(other) {
    if (other !== person) {
      var resources = {};
      resource_names.forEach(resource => {
        resources [resource] = Math.random()*2-1;
      });
      person.relationships [other.index] = {received_resources: resources}
    }
  });
  
    /*person.heading = Math.random()*turn;
    person.y += default_speed/frames_per_second*Math.cos(person.heading) ;
    person.x += default_speed/frames_per_second*Math.sin (person.heading) ;
    draw_person (person);*/
});





function draw_person (person) {
  
  canvas_context.beginPath();
  canvas_context.arc (person.x, person.y, person_radius* 0.95, 0, turn, true);
  canvas_context.lineWidth = person_radius* 0.1;
  
  close_shape ("rgb("+Math.floor(255*person.resources.energy.immediate) +", 255, 255)", "rgb("+Math.floor(255*person.resources.companionship.immediate) +",0,0)");
  
  var relationship = person.relationships [person.target_index];
  canvas_context.beginPath();
  canvas_context.moveTo(person.x, person.y);
  canvas_context.lineWidth = person_radius* (0.02+0.3*person.target_desire);
  canvas_context.lineTo(person.x+ person_radius* 0.6*Math.cos(person.heading), person.y+ person_radius* 0.6*Math.sin(person.heading));
  canvas_context.strokeStyle = "rgb("+Math.floor(255*relationship.received_resources.companionship) +",0,0)";
  canvas_context.stroke();
  
}

function desire (person, other) {
  var relationship = person.relationships [other.index];
  var result = 0;
  resource_names.forEach(resource => {
    result += (1 - person.resources [resource].immediate)*relationship.received_resources [resource];
  });
  //console.log(result);
  return result / resource_names.length;
}
function distance (person, other) {
  return Math.sqrt ((person.x - other.x)*(person.x - other.x) + (person.y - other.y)*(person.y - other.y));
}

function interact (person, other) {
  var relationship = person.relationships [other.index];
  resource_names.forEach(resource => {
    person.resources [resource].immediate += relationship.received_resources [resource]/frames_per_second;
    if (person.resources [resource].immediate <0) {
      person.resources [resource].immediate = 0;
    }
    if (person.resources [resource].immediate >1) {
      person.resources [resource].immediate = 1;
    }
  });
}
function time_passes (person) {
  resource_names.forEach(resource => {
    person.resources [resource].immediate -= resource_decay/frames_per_second;
    if (person.resources [resource].immediate <0) {
      person.resources [resource].immediate = 0;
    }
  });
}

function update_target (person, other) {
  person.target_index = other.index;
  person.target_desire = desire (person, other);
  person.target_distance = distance (person, other);
}





//resources.forEach(function(




var start = Date.now();
var step = 0;

function tick() {
  requestAnimationFrame (tick);
  step++;
  var updated = update_dimensions();
  var width = game_width;
  var height = game_height;
  var time = step/frames_per_second;//(Date.now() - start)/1000;  
  
  canvas_context.clearRect (0, 0, width, height);
  
  canvas_context.save();
  canvas_context.translate (width*0.05, height*0.05);
  canvas_context.scale (width*0.9, height*0.9);
  
  
  people.forEach(function(person) {
    person.neighbors =[];
    person.avoidance = {x:0,y:0};
    if (person.target_index !== undefined) {update_target (person, people [person.target_index]);}
    people.forEach(function(other) {
      if (other !== person) {
        var other_desire = desire (person, other);
        var other_distance = distance (person, other);
        var heading = Math.atan2 (other.y - person.y, other.x - person.x);
        
        if ((person.target_index === undefined) || (other_desire > person.target_desire + indifference_threshold)) {
          update_target (person, other);
        }
        
        if (other_distance <= interaction_distance*2) {
          var factor = Math.min (1, 2 - other_distance/interaction_distance);
          // don't push other people on purpose just because you like them
          if (other_desire>0) {
            factor = Math.min (factor, (other_distance-personal_space_distance)/(interaction_distance-personal_space_distance));
          }
          person.avoidance.x += factor*other_desire*Math.cos(heading);
          person.avoidance.y += factor*other_desire*Math.sin(heading);
        }
        if (other_distance <= interaction_distance) {
          person.neighbors.push (other);
        }
        //step away from people who are actually bumping into you
        if (other_distance < person_radius*2) {
          var factor = 2*(1 - other_distance/(person_radius*2));
          person.avoidance.x -= factor*Math.cos(heading);
          person.avoidance.y -= factor*Math.sin(heading);
        }
      }
    });
    var target = people [person.target_index];
    person.heading = Math.atan2 (target.y - person.y, target.x - person.x);
  });
  people.forEach(function(person) {
    var speed = default_speed*person.target_desire;
    person.x += speed/frames_per_second*Math.cos(person.heading) ;
    person.y += speed/frames_per_second*Math.sin (person.heading) ;
  });
  people.forEach(function(person) {
    person.neighbors.forEach(function(other) {
      interact (person, other);
    });
    time_passes (person);
    
    var max_avoidance = default_speed*3;
    var current_avoidance = Math.sqrt ((person.avoidance.x)*(person.avoidance.x) + (person.avoidance.y)*(person.avoidance.y));
    if (current_avoidance >max_avoidance) {
      person.avoidance.x *= max_avoidance/current_avoidance;
      person.avoidance.y *= max_avoidance/current_avoidance;
    }
    

    
    person.x += person.avoidance.x/frames_per_second;
    person.y += person.avoidance.y/frames_per_second;
    if (person.x <0) {person.x = 0 + (person.x - 0)*0.5;}
    if (person.x >1) {person.x = 1 + (person.x - 1)*0.5;}
    if (person.y <0) {person.y = 0 + (person.y - 0)*0.5;}
    if (person.y >1) {person.y = 1 + (person.y - 1)*0.5;}
    draw_person (person);
  });
  
  canvas_context.restore();  
}
tick();


});
    </script>''',
      '''<a class="skip" href="#content">Skip to content</a>
      '''+bars.bars_wrap({"games":True}, '''<main><div id="content">
      
      <div id="game"></div>

    '''+
    # blog.comments_section ("the_path") +
    '''
  </div>
</main>'''), {
  "jQuery_before": True,
  "blurb": "A unfinished online game.",
  #"blurb_image": "/media/pac-asteroids-thumbnail.png?rr"
}
  )
  
Esempio n. 16
0
def add_game(page_dict):
  utils.make_page (page_dict,
    '/hexy',
      "Hexy Bondage ⊂ Eli Dupree's website",
      r'''
<style>
html.hexy .bars_outer_box {
  background-color: #878787;
  background-image: url("/media/hexy-background.jpg?rr"); }

div.hexy_content h1{ font-size: 200%; font-weight: bold; padding-top: 0.20em; }
div.hexy_content h2{ font-size: 150%; font-weight: bold; padding-top: 0.30em; }
div.hexy_content p { margin: 0; padding: 0.5em 0; line-height: inherit; }
div.hexy_content a:link { color:blue }
div.hexy_content a:visited { color:purple }

div.hexy_content div.subtitle { font-weight: bold; font-size: 90%; padding-bottom: 0.5em;}
div.hexy_content {
  text-align: center;
  font-family: Arial, Helvetica, sans-serif;
  max-width: 50em;
  margin: 0 auto; }
div.hexy_content form,div.hexy_content table {
  display: inline; }
div.hexy_content .paynowbutton {
  vertical-align: middle; }
div.hexy_content div.bigbox_outer {
  padding: 0.2em 0.4em; }
div.hexy_content div.bigbox {
  border-radius: 1.4em;
  padding: 0.3em;
  background-color: #ffffff;
  background-color: rgba(255,255,255,0.95); }
div.hexy_content div.bigbox.thankyou {
  padding: 1.0em 0.3em;
  background-color: #ffff00;
  background-color: rgba(255,255,0,0.90); }
div.hexy_content div.bigbox_outer.narrower { display:inline-block; }
div.hexy_content div.bigbox_outer.last { padding-bottom: 0.4em; }
div.hexy_content div.bigbox_outer:first-child { padding-top: 0.4em; }

div.hexy_content div.bigbox_outer.comments {
  font-size: 80%;
  font-family: Times New Roman, Times, serif; }
div.hexy_content div.bigbox.comments {
  padding-bottom: 1.0em; }
div.hexy_content .important_link {
  font-size: 160%; font-weight: bold; }

@media (min-width: 30em) {
  div.hexy_content div.bigbox_outer {
    padding: 0.6em 1.8em; }
  div.hexy_content div.bigbox {
    border-radius: 2.2em;
    padding: 0.7em 1em;
  }
  div.hexy_content div.bigbox.thankyou {
    padding: 1.8em 1em; }
  div.hexy_content div.bigbox_outer.last { padding-bottom: 1.2em; }
  div.hexy_content div.bigbox_outer:first-child { padding-top: 1.2em; }
  div.hexy_content {
    font-size: 1.2em; 
  }
  div.hexy_content div.bigbox_outer.comments {
    font-size: 70%; }
}
@media (min-width: 13em) {
  div.hexy_content a.lesswrap {
    white-space: nowrap;
  }
}
</style>
''',
      '''<a class="skip" href="#content">Skip to content</a>
      '''+bars.bars_wrap({"games":True}, '''<main>
  <div id="content" class="hexy_content">
<div class="bigbox_outer">
<div class="bigbox">
  <h1>Hexy Bondage</h1>
  <div class="subtitle">'''+ blurb +'''</div>
  <p>
Hexy Bondage is a game where in-game actions cause you to be tied up in real life.
  </p>
  <p>
Players take turns placing tiles. Each tile has an icon (hand, foot, torso, crotch, furniture...)
and some paths. The paths can be used to connect icons together. Connections cause
things to happen in real life, like tying your body parts together. A player wins
when their “opponent” is too tied up to reach the board.
  </p>
  <p><a class=" important_link lesswrap" href="/hexy/hexy-bondage-rules.pdf">Read the full rules (pdf)</a></p>
  <p> <a class=" important_link lesswrap" href="/hexy/hexy-bondage-tiles.pdf">Download printable tiles</a> </p>
  <p class="hidden_from_restricted_users">If you like this game, consider <a href="https://www.patreon.com/EliDupree">supporting me on Patreon</a> so that I can continue making awesome things and sharing them for free on the Internet.</p>
</div>
</div>
<div class="bigbox_outer">
<div class="bigbox">
  <h2>Extras:</h2>
  <p>Tile sheets for extra players:
    <a class="lesswrap" href="/hexy/hexy-bondage-extra-tiles-green.pdf">green checkers</a>,
    <a class="lesswrap" href="/hexy/hexy-bondage-extra-tiles-pink.pdf">pink polka dots</a>,
    <a class="lesswrap" href="/hexy/hexy-bondage-extra-tiles-blue.pdf">blue waves</a>,
    <a class="lesswrap" href="/hexy/hexy-bondage-extra-tiles-purple.pdf">purple stripes</a></p>
  <p>Black-and-white versions of tiles for extra players:
    <a href="/hexy/hexy-bondage-extra-tiles-checkers.pdf">checkers</a>,
    <a href="/hexy/hexy-bondage-extra-tiles-polkadots.pdf">polka dots</a>,
    <a href="/hexy/hexy-bondage-extra-tiles-waves.pdf">waves</a>,
    <a href="/hexy/hexy-bondage-extra-tiles-stripes.pdf">stripes</a></p>
  <p><a href="/hexy/hexy-bondage-blank-tiles.pdf">Sheet of blank tiles</a></p>
</div>
</div>
<div class="bigbox_outer narrower comments last">
<div class="bigbox narrower comments last">
  '''+blog.comments_section("hexy")+'''
</div>
</div>
  </div>
</main>'''), {"html_class":"hexy", "blurb": blurb + ".", "blurb_image": "/media/hexy-thumbnail.png?rr"}
  )
  
  utils.make_page (page_dict,
    '/hexy-future',
      "Hexy Bondage ⊂ Eli Dupree's website",
      r'''
<style>

.tile {
  transition-duration: 0.6s;
}
button {
  font-size: 120%;
  padding: 3px 5px;
  border:2px solid black;
  border-radius:5px;
}

</style>
<script src="https://unpkg.com/react@latest/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@latest/dist/react-dom.js"></script>
<script src="https://unpkg.com/[email protected]/babel.min.js"></script>
''',
      '''<a class="skip" href="#content">Skip to content</a>
      '''+bars.bars_wrap({"games":True}, '''<main>
  <div id="content">
    <!-- <div id="messages"></div> -->
    
    <!-- <div id="tile_controls"></div> -->
  </div>
  '''+ trimmed_svg+'''
</main>'''), {"html_class":"hexy", "blurb": blurb + ".", "blurb_image": "/media/hexy-thumbnail.png?rr", "after_body":'''

     <script type="text/javascript" src="/media/lodash.min.js?rr"></script>

     <script type="text/javascript">
       window.hexy_tile_ids = ['''+ (",".join (['"'+id+'"' for id in tile_ids])) +''']
     </script>
     
     <!-- <script type="text/javascript" src="/media/hexy.js?rr"></script> -->
     <script type="text/babel" src="/media/hexy-react.js?rr">
'''}
  )
Esempio n. 17
0
def add_game(page_dict):
  utils.make_page (page_dict,
    '/hexy-classic',
      "Hexy Bondage ⊂ Eli Dupree's website",
      r'''
<style>
html.hexy .bars_outer_box {
  background-color: #878787;
  background-image: url("/media/hexy-background.jpg?rr"); }

div.hexy_content h1{ font-size: 200%; font-weight: bold; padding-top: 0.20em; }
div.hexy_content h2{ font-size: 150%; font-weight: bold; padding-top: 0.30em; }
div.hexy_content p { margin: 0; padding: 0.5em 0; line-height: inherit; }
div.hexy_content a:link { color:blue }
div.hexy_content a:visited { color:purple }

div.hexy_content div.subtitle { font-weight: bold; font-size: 90%; padding-bottom: 0.5em;}
div.hexy_content {
  text-align: center;
  font-family: Arial, Helvetica, sans-serif;
  max-width: 50em;
  margin: 0 auto; }
div.hexy_content form,div.hexy_content table {
  display: inline; }
div.hexy_content .paynowbutton {
  vertical-align: middle; }
div.hexy_content div.bigbox_outer {
  padding: 0.2em 0.4em; }
div.hexy_content div.bigbox {
  border-radius: 1.4em;
  padding: 0.3em;
  background-color: #ffffff;
  background-color: rgba(255,255,255,0.95); }
div.hexy_content div.bigbox.thankyou {
  padding: 1.0em 0.3em;
  background-color: #ffff00;
  background-color: rgba(255,255,0,0.90); }
div.hexy_content div.bigbox_outer.narrower { display:inline-block; }
div.hexy_content div.bigbox_outer.last { padding-bottom: 0.4em; }
div.hexy_content div.bigbox_outer:first-child { padding-top: 0.4em; }

div.hexy_content div.bigbox_outer.comments {
  font-size: 80%;
  font-family: Times New Roman, Times, serif; }
div.hexy_content div.bigbox.comments {
  padding-bottom: 1.0em; }
div.hexy_content .important_link {
  font-size: 160%; font-weight: bold; }

@media (min-width: 30em) {
  div.hexy_content div.bigbox_outer {
    padding: 0.6em 1.8em; }
  div.hexy_content div.bigbox {
    border-radius: 2.2em;
    padding: 0.7em 1em;
  }
  div.hexy_content div.bigbox.thankyou {
    padding: 1.8em 1em; }
  div.hexy_content div.bigbox_outer.last { padding-bottom: 1.2em; }
  div.hexy_content div.bigbox_outer:first-child { padding-top: 1.2em; }
  div.hexy_content {
    font-size: 1.2em; 
  }
  div.hexy_content div.bigbox_outer.comments {
    font-size: 70%; }
}
@media (min-width: 13em) {
  div.hexy_content a.lesswrap {
    white-space: nowrap;
  }
}
</style>
''',
      '''<a class="skip" href="#content">Skip to content</a>
      '''+bars.bars_wrap({"games":True}, '''<main>
  <div id="content" class="hexy_content">
<div class="bigbox_outer">
<div class="bigbox">
  <h1>Hexy Bondage</h1>
  <div class="subtitle">'''+ blurb +'''</div>
  <p>
Hexy Bondage is a game where in-game actions cause you to be tied up in real life.
  </p>
  <p>
Players take turns placing tiles. Each tile has an icon (hand, foot, torso, crotch, furniture...)
and some paths. The paths can be used to connect icons together. Connections cause
things to happen in real life, like tying your body parts together. A player wins
when their “opponent” is too tied up to reach the board.
  </p>
  <p>In June 2017, I made <a href="/hexy">a web version of Hexy Bondage for players to play together on the same device</a>. The web version is generally better than this printable version. But I'm leaving this older, printable version available in case you find it useful.</p>
  <p><a class=" important_link lesswrap" href="/hexy/hexy-bondage-rules.pdf">Read the full rules (pdf)</a></p>
  <p> <a class=" important_link lesswrap" href="/hexy/hexy-bondage-tiles.pdf">Download printable tiles</a> </p>
  
  <p class="hidden_from_restricted_users">If you like this game, consider <a href="https://www.patreon.com/EliDupree">supporting me on Patreon</a> so that I can continue making awesome things and sharing them for free on the Internet.</p>
</div>
</div>
<div class="bigbox_outer">
<div class="bigbox">
  <h2>Extras:</h2>
  <p>Tile sheets for extra players:
    <a class="lesswrap" href="/hexy/hexy-bondage-extra-tiles-green.pdf">green checkers</a>,
    <a class="lesswrap" href="/hexy/hexy-bondage-extra-tiles-pink.pdf">pink polka dots</a>,
    <a class="lesswrap" href="/hexy/hexy-bondage-extra-tiles-blue.pdf">blue waves</a>,
    <a class="lesswrap" href="/hexy/hexy-bondage-extra-tiles-purple.pdf">purple stripes</a></p>
  <p>Black-and-white versions of tiles for extra players:
    <a href="/hexy/hexy-bondage-extra-tiles-checkers.pdf">checkers</a>,
    <a href="/hexy/hexy-bondage-extra-tiles-polkadots.pdf">polka dots</a>,
    <a href="/hexy/hexy-bondage-extra-tiles-waves.pdf">waves</a>,
    <a href="/hexy/hexy-bondage-extra-tiles-stripes.pdf">stripes</a></p>
  <p><a href="/hexy/hexy-bondage-blank-tiles.pdf">Sheet of blank tiles</a></p>
</div>
</div>
<div class="bigbox_outer narrower comments last">
<div class="bigbox narrower comments last">
  '''+blog.comments_section("hexy_classic")+'''
</div>
</div>
  </div>
</main>'''), {"html_class":"hexy", "blurb": blurb + ".", "blurb_image": "/media/hexy-thumbnail.png?rr"}
  )
  
  """
  utils.make_page (page_dict,
    '/hexy-future',
      "Hexy Bondage ⊂ Eli Dupree's website",
      r'''
<style>

.tile {
  transition-duration: 0.6s;
}
.rotation_arrow {
  --arrow-fill: #ffffff;
}
.rotation_arrow:hover {
  --arrow-fill: #ffff00;
}
button {
  font-size: 120%;
  padding: 3px 5px;
  border:2px solid black;
  border-radius:5px;
}

</style>
<script src="https://unpkg.com/react@latest/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@latest/dist/react-dom.js"></script>
<script src="https://unpkg.com/[email protected]/babel.min.js"></script>
''',
      '''<a class="skip" href="#content">Skip to content</a>
      '''+bars.bars_wrap({"games":True}, '''<main>
  <div id="content">
    <!-- <div id="messages"></div> -->
    
    <!-- <div id="tile_controls"></div> -->
  </div>
  '''+ trimmed_svg+'''
</main>'''), {"html_class":"hexy", "blurb": blurb + ".", "blurb_image": "/media/hexy-thumbnail.png?rr", "after_body":'''

     <script type="text/javascript" src="/media/lodash.min.js?rr"></script>

     <script type="text/javascript">
       window.hexy_tile_ids = ['''+ (",".join (['"'+id+'"' for id in tile_ids])) +''']
     </script>
     
     <!-- <script type="text/javascript" src="/media/hexy.js?rr"></script> -->
     <script type="text/babel" src="/media/hexy-react.js?rr">
'''}
  )
  """
  
  utils.make_page (page_dict,
    '/hexy',
      "Hexy Bondage ⊂ Eli Dupree's website",
      r'''
<style>

html,body {
  background-color: var(--meta-fill);
  --hex-fill: #ffffff;
  --hex-stroke: transparent;
  --meta-stroke: black;
  --meta-fill: #ccc;
}
.game_svg {
  display: block;
  /*margin: 0 auto;*/
  /* prevent it from overflowing its box before being sized */
  width: 0; height: 0;
}
.message_area {
  /*background-color:#ffaaaa;*/
  margin: 0.2em;
  padding: 0.5em;
  border-radius: 1em;
  border: 0.2em solid var(--meta-stroke);
  /*color: var(--meta-stroke);*/
  font-family: Arial, Helvetica, sans-serif;
  flex-grow: 1;
}
.message_area p {
  margin: 0;
  line-height: 1.10em;
}
.message_area p~p {
  margin-top: 0.5em;
}
.tile {
  /*transition-duration: 0.6s;*/
}
.prompt_options {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}
.prompt_option {
  font-size: 100%;
  margin: 0.5em;
  white-space: normal;
}

input[type="button"],.fake_button {
  background-color: var(--meta-fill);
  border: 0.2em solid var(--meta-stroke);
  padding: 0.5em;
  border-radius: 1em;
  color: var(--meta-stroke);
  font-family: Arial, Helvetica, sans-serif;
  cursor: pointer;
}
input[type="button"]:hover,.fake_button:hover {
  background-color: var(--meta-stroke);
  border-color: 0.2em solid var(--meta-fill);
  color: var(--meta-fill);
}

.board_container {
  width: 100%;
  overflow: auto;
}
.non_board_container {
  display: flex;
  font-size: 3vh;
}
.buttons_area {
  display: flex;
  flex-direction: column;
}
.buttons_area input[type="button"] {
  display: block;
  font-size: 100%;
  padding: 0.3em;
  margin: 0.2em;}


#menu_wrapper {
  position: fixed;
  left: 0; right: 0; top: 0; bottom: 0;
}
#menu {
  position: fixed;
  left: 5%; right: 5%; top: 8%; bottom: 5%;
  border: 1.5vh solid black; border-radius: 6vh;
  box-shadow: 10px 5px 15px #fff;
  background-color: white;
  --meta-stroke: black;
  --meta-fill: white;
  text-align: center;
}
h1 {font-size: 200%;}
h2 {font-size: 150%; margin: 0.5em;}
#menu p {}
#menu ul {text-align: left; line-height: 1.35em;}
#menu_contents {
  padding: 2vh;
  overflow: auto;
}
#menu_navigation {
  padding: 0.8em 0.2em;
  background-color: #aaa;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}

.modes {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  max-width: 40em;
  margin: 0 auto;
  text-align: left;
}
.mode_box {
  margin:0.2em;
}
.modes input {
  width:2em; height:2em; vertical-align: middle;
}
.modes label {
  vertical-align: middle; margin-left:0.2em;
}

.players {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.player_options {
  margin: 0.2em;
  border-radius:0.4em;
  padding:0.5em;
  background-color:#ddd;
}
.player_options input[type="text"] {
  width: 6em;
}

.fake_boards {
  display: flex ;
  justify-content: center;
}
.fake_board {
  margin: 0.6em;
  flex-shrink: 0;
}

.connection_type {
  display: inline-block;
  margin: 0.6em;
  max-width: 40em;
  background-color:#ccc;
}
@media screen and (max-width: 400px) {
  .fake_board {
    margin: 0.1em;
    flex-shrink: 0;
  }
  .connection_type {
    margin: 0.6em -0.2em;
  }
}
.connection_type p {
  padding: 0 1.1em;
}
.connection_type .fake_board {
  margin: 0.1em;
}

#comments {
  display: none;
}

.noscript {
  margin: 3em auto;
  padding: 0 1.1em;
  max-width: 40em;
  font-size: 130%;
}

</style>
''',
      '''<a class="skip" href="#content">Skip to content</a>
      '''+bars.bars_wrap({"games":True}, '''<main>
  <div id="content">
    <div class="noscript">This game requires JavaScript to play. To play, enable JavaScript in your browser and reload the page. Alternatively, look at <a href="/hexy-classic">the older printable board game</a>, which doesn't require JavaScript.
    <!-- <div id="messages"></div> -->
    
    <!-- <div id="tile_controls"></div> -->
  </div>
  '''+blog.comments_section("hexy")+'''
  '''+ trimmed_svg+'''
</main>'''), {"html_class":"hexy", "blurb": "A sexual game for two players (or more) to play together on the same device.", "blurb_image": "/media/hexy-thumbnail.png?rr", "after_body":'''

     <script type="text/javascript" src="/media/lodash.min.js?rr"></script>

     <script type="text/javascript">
       window.hexy_tile_ids = ['''+ (",".join (['"'+id+'"' for id in tile_ids])) +''']
     </script>
     
     <script type="text/javascript" src="/media/hexy-3-mechanics.js?rr"></script>
     <script type="text/javascript" src="/media/hexy-3-game-modes.js?rr"></script>
     <script type="text/javascript" src="/media/hexy-3-game-ui.js?rr"></script>
     <script type="text/javascript" src="/media/hexy-3-meta-ui.js?rr"></script>
'''}
  )
def add_page(page_dict):
  utils.make_page (page_dict,
    '/codecophony',
      "Codecophony: compose music through programming ⊂ Eli Dupree's website",
      r'''<style type="text/css">
html,body {background-color: white;}
div.recording {display: inline-block; margin:3px;/* padding-left:12px;*/ background-color:#ccc;}
canvas.recording {display: block; cursor: pointer;}
#recent_magnitudes {display: block; cursor: pointer;}
.control_panels {display: inline-block; vertical-align: middle;}
.control_panel {display: inline-block; background-color: transparent; margin:3px; vertical-align: top;}
.control {display: inline-block; background-color:#ccc; color:#555; font-weight: bold; padding:4px; vertical-align: top; cursor: pointer;}
.control.selected {background-color:#5f5; color:#000;}
.text-danger {color:#d9534f;}
.recent_box {float: right; background-color:#ccc;}
.recent_magnitudes_caption {padding:4px;}
.recording_button {padding:0 3px; cursor: pointer;}
.recordings,.generated {
    display: flex;
    flex-wrap: wrap-reverse;
}
.page_description {padding:0.3em 2em; font-size: 110%;}

.project_editor {display: none;}
.project_editor_inner,.tool_box_top {
display: flex;
justify-content: space-between;
}
.tool_box,.codecophony_space {
flex: 1 1 50%;
min-width: 0;
}

.project_select {
  padding: 4%;
}
.project_select p {
  text-align: center;
  font-size: 130%;
}
.project_select h1 {
  text-align: center;
}
.project_select h2 {
  text-align: center;
  font-weight: bold;
  font-size: 150%;
}
.project_select button {
  font-weight: bold;
  font-size: 150%;
}
.project_select code {
  white-space: nowrap;
  background-color: #ddd;
}
.generated {
  border-top:3px solid black;
}
div.item {display: inline-block; margin:3px; background-color:#bbb;}

textarea {
  width: 96%;
}
textarea:focus {
  height: 60vh;
}

#sandbox {display: none;}

.fa-stack.force_small {width:1em; height:1em; line-height:0.7em;}
span.no_underlay {position: absolute; top: 0; left: 0; z-index: 1;}
    </style> 
    <link rel="stylesheet" href="/media/font-awesome-4.6.3/css/font-awesome.min.css?rr">
    ''',
      '''<a class="skip" href="#content">Skip to content</a>
      '''+bars.bars_wrap({"games":True}, '''
<main>
  <div class="project_select ">
    <h1>Welcome to Codecophony! This is an early prototype.</h1>
    <p>Codecophony is a tool to compose music through programming, although it may be usable by non-programmers as well.</p>
    <p>Create a new "project" using the button below, then edit it using the "edit" button. Then you'll see the edit screen. The left side lets you record sounds just like my <a href="/voice-practice-tool">voice practice tool</a>. The right side lets you create scripts. Here's an example script (try it out!):</p>
    <pre>var notes = codecophony.scrawl (
  `with instrument acoustic_guitar_steel duration 0.28

play 0 then 2 then 3 then 5 then 7 lasting 4
[with pitch -7 start -0.05 instrument trumpet
  play 0 then 2 then 3 then 5 then 7 lasting 4
]
[with pitch 0 start -0.02
  play -3 0 then -4 2 then -3 3 then -4 5 then -4 7 lasting 4
]
`
)
items.example_notes = notes;
var example_sequence = items.example_sequence = codecophony.render_notes (notes);
example_sequence.start += 4800;
var a = items.input_sequence;
codecophony.amplify (a, 0.14);
items.combined_sequence = codecophony.add_sequences ([example_sequence, a]);
</pre>
  
  <p>For this script to work, you will also need to import the <code>trumpet</code> and <code>acoustic_guitar_steel</code> instruments using the menu in the top right, and record something and rename the recording "input_sequence".</p>
  
  <p>These scripts are written in JavaScript. <code>codecophony</code> is a table containing various utility functions. <code>items</code> is a proxy table that lets you examine the "items" (recordings and other stuff) that you see on screen, and create new ones. A "sequence" is either a Float32Array, like those used by the web audio API, or <code>{start: &lt;starting time in number of 44100 Hz samples&gt;, data: Float32Array}</code>. lodash is available as <code>_</code>.</p>
  
  <h2>The codecophony.scrawl format:</h2>
  <p><code>play</code> starts a new note at the default start time. <code>and</code> starts a new note at the same time as the last note you made. <code>then</code> starts a new note immediately after the last note ends. <code>with</code> sets defaults for all notes that come after it, until the end of the current [] block.</p>
  
  <p>Everything else sets the <em>attributes</em> of the note objects. <code>pitch</code> is additive, measured in number of semitones, starting at middle C. <code>duration</code> and <code>volume</code> are multiplicative. Everything else (like <code>instrument</code>) just overwrites its previous value. A number with no attribute name in front of it means pitch by default. Times are measured in seconds.</p>
  
  <p>A few other attributes exist. I'll eventually write full documentation.</p>
  
  <p>Your creations will be autosaved in your browser. I will eventually make a way to import and export projects, but I haven't yet. Right now, you can only copy/paste the scripts and save the generated sound files.</p>
  
  </div>
  <div class="project_editor "><div class="project_editor_inner ">
  <div class="tool_box">
    <div class="tool_box_top">
      <canvas id="histogram_canvas" width="320" height="80">
The histogram should appear here, but it hasn't. Maybe you don't have JavaScript enabled. Or maybe your browser doesn't support the canvas element.
      </canvas>
      <div class="recent_box">
        <div class="recent_magnitudes_caption "></div>
        <canvas id="recent_magnitudes"></canvas>
      </div>
    </div>
    <div class="control_panels "></div>
    <div class="recordings "></div>
    <div class="generated "></div>
  </div>
  <div class="codecophony_space"></div>
  </div></div>
</main>
'''), {"blurb": blurb, "blurb_image": blurb_image, "after_body":'''
     <script type="text/javascript" src="/media/audiobuffer-to-wav.js?rr"></script>
     <script type="text/javascript" src="/media/download.js?rr"></script>
     <script type="text/javascript" src="/media/jszip.min.js?rr"></script>
     
     <!--<script type="text/javascript" src="/media/complex.js?rr"></script>
     <script type="text/javascript" src="/media/pitch.js?rr"></script>-->
     <script type="text/javascript" src="/media/pitchdetect.js?rr"></script>
     <script type="text/javascript" src="/media/audio-loader.min.js?rr"></script>
     
     <script type="text/javascript" src="/media/sound-processing-utils.js?rr"></script>
     <script type="text/javascript" src="/media/voice-practice-tool-lib.js?rr"></script>
    
     <script type="text/javascript" src="/media/codecophony.js?rr"></script>
'''}
  )