1
0
Fork 0
mirror of https://gitlab.com/Oreolek/black_phone.git synced 2024-06-17 07:30:57 +03:00

Initial commit

This commit is contained in:
Alexander Yakovlev 2015-12-29 13:56:27 +07:00
commit cda4cd7262
12 changed files with 738 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
node_modules
build
dist
dist.zip

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "sass/bootstrap"]
path = sass/bootstrap
url = https://github.com/twbs/bootstrap.git

180
Gulpfile.js Normal file
View file

@ -0,0 +1,180 @@
'use strict';
/* Imports */
var watchify = require('watchify'),
browserify = require('browserify'),
browserSync = require('browser-sync'),
gulp = require('gulp'),
source = require('vinyl-source-stream'),
gutil = require('gulp-util'),
coffeify = require('coffeeify'),
sass = require('gulp-sass'),
minifyCSS = require('gulp-minify-css'),
uglify = require('gulp-uglify'),
buffer = require('vinyl-buffer'),
zip = require('gulp-zip'),
_ = require('lodash'),
concat = require('gulp-concat');
var reload = browserSync.reload;
/* Tasks */
/* Trivial file copies */
function html (target) {
return function () {
return gulp.src(['html/index.html','html/en.html'])
.pipe(gulp.dest(target));
};
}
function img (target) {
return function () {
return gulp.src(['img/*.png', 'img/*.jpeg', 'img/*.jpg'])
.pipe(gulp.dest(target));
};
}
function audio (target) {
return function () {
return gulp.src(['audio/*.mp3'])
.pipe(gulp.dest(target));
};
}
gulp.task('html', html('./build'));
gulp.task('img', img('./build/img'));
gulp.task('audio', audio('./build/audio'));
/* Less */
gulp.task('sass', function () {
gulp.src('sass/main.scss')
.pipe(sass().on('error', sass.logError))
.pipe(gulp.dest('./build/css'));
});
/* Bundle libraries */
var undumBundler = browserify({debug: true});
undumBundler.require('undum-commonjs');
gulp.task('buildUndum', function () {
return undumBundler.bundle().pipe(source('undum.js')).pipe(gulp.dest('./build/game'));
});
/* Generate JavaScript with browser sync. */
var sources = [
'./game/begin.coffee',
'./game/story.coffee',
'./game/end.coffee',
]
var opts = _.assign({}, watchify.args, {
entries: ["./build/game/main.coffee"],
debug: true,
transform: [coffeify]
});
var bundler = watchify(browserify(opts));
bundler.external('undum-commonjs');
function bundle () {
return bundler.bundle()
.on('error', gutil.log.bind(gutil, 'Browserify Error'))
.pipe(source('bundle.js'))
.pipe(gulp.dest('./build/game'));
};
gulp.task('concatCoffee', function() {
return gulp.src(sources)
.pipe(concat('./main.coffee'))
.pipe(gulp.dest('./build/game'));
});
// `gulp coffee` will generate bundle
gulp.task('coffee', ['buildUndum', 'concatCoffee'], bundle);
bundler.on('update', bundle); // Re-bundle on dep updates
bundler.on('log', gutil.log); // Output build logs to terminal
/* Make a development build */
gulp.task('build', ['html', 'img', 'sass', 'coffee', 'audio'], function () {
});
/* Start a development server */
gulp.task('serve', ['build'], function () {
browserSync({
server: {
baseDir: 'build'
}
});
var sassListener = function () {
reload('./build/css/main.css');
}
gulp.watch(['./html/*.html'], ['html']);
gulp.watch(['./sass/*.scss'], ['sass']);
gulp.watch(['./img/*.png', './img/*.jpeg', './img/*.jpg'], ['img']);
gulp.watch(['./game/*.coffee'], ['coffee']);
gulp.watch(['./build/css/main.css'], sassListener);
gulp.watch(
['./build/game/bundle.js', './build/img/*', './build/index.html'],
browserSync.reload);
});
/* Distribution tasks */
var undumDistBundler = browserify();
undumDistBundler.require('undum-commonjs');
gulp.task('undum-dist', function () {
return undumDistBundler.bundle().pipe(source('undum.js'))
.pipe(buffer())
.pipe(uglify())
.pipe(gulp.dest('./dist/game'));
});
gulp.task('html-dist', html('./dist'));
gulp.task('img-dist', img('./dist/img'));
gulp.task('audio-dist', audio('./dist/audio'));
gulp.task('sass-dist', function () {
return gulp.src('./sass/main.scss')
.pipe(sass({outputStyle: 'compressed'}))
.pipe(gulp.dest('./dist/css'));
});
var distBundler = browserify({
debug: false,
entries: ['./build/game/main.coffee'],
transform: ['coffeeify']
});
distBundler.external('undum-commonjs');
gulp.task('coffee-dist', ['undum-dist', 'concatCoffee'], function () {
return distBundler.bundle()
.pipe(source('bundle.js'))
.pipe(buffer())
.pipe(uglify())
.on('error', gutil.log)
.pipe(gulp.dest('./dist/game'));
});
gulp.task('dist', ['html-dist', 'img-dist', 'sass-dist', 'coffee-dist', 'audio-dist'],
function () {
return;
});
gulp.task('zip', ['dist'], function () {
return gulp.src('dist/**')
.pipe(zip('dist.zip'))
.pipe(gulp.dest('.'));
});

71
game/begin.coffee Normal file
View file

@ -0,0 +1,71 @@
# copyright (c) Alexander Yakovlev 2015.
# This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
# To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0
situation = require('raconteur')
undum = require('undum-commonjs')
oneOf = require('raconteur/lib/oneOf.js')
qualities = require('raconteur/lib/qualities.js')
$ = require("jquery")
Array.prototype.oneOf = () ->
oneOf.apply(null, this)
md = require('markdown-it')
markdown = new md({
typographer: true,
html: true
})
shortid = require('shortid')
# you have to alter linkRe in Undum core to use that.
# Undum doesn't allow using uppercase letters in situation names by default.
undum.game.id = "6a9909a4-586a-4089-bd18-26da684d1c8d"
undum.game.version = "1.0"
a = require('raconteur/lib/elements.js').a
way_to = (content, ref) -> a(content).class('way').ref(ref)
textlink = (content, ref) -> a(content).once().writer(ref)
textcycle = (content, ref) -> a(content).replacer(ref).class("cycle").id(ref).toString()
# usage: writemd( system, "Text to write")
writemd = (system, text) ->
if typeof text is Function
text = text()
system.write(markdown.render(text))
money = (character, system, amount) ->
system.setQuality("money", character.qualities.money + amount)
situation "start",
content: """
Peter had so much trouble sleeping he had to drown his pills in at least an hour of thoughts.
A violent ringing of the bell awakened him.
He rose from the bed, grumbling:
Crazy neighbors and their guests. It must be three o'clock!”
The visitor entered the hallway.
It was him ringing the bell, but he was not going to meet Peter.
In fact, he wasn't looking for meeting anybody here.
Fourth floor, apartment 406.
There, he tried four keys.
The third of them fitted the lock.
Burglary is a curious line of employment.
Befittedly, Ronald Chernoff was very curious about a black phone behind the door of apartment 406 in a wooden box on a small table no farther than two meters from the bed.
A gift, a prototype, a valuable treasure left by Anastacia Kozlowa when she fled the country.
Of course, one had to be reasonably au fait with her *Instagram* to notice that.
Peter opened his door to find an empty silent corridor.
He went to the neighbor's door and met a closed door.
Ronald was working inside, quietly walking around the apartment.
He began the inspection from [the living room.](living-room)
"""
is_visited = (situation) ->
situations = undum.game.situations[situation]
if situations
return Boolean situations.visited
return 0

9
game/end.coffee Normal file
View file

@ -0,0 +1,9 @@
qualities
undum.game.init = (character, system) ->
$("#ways").on("click", "a", (event) ->
event.preventDefault()
undum.processClick($(this).attr("href"))
)
window.onload = undum.begin

100
game/story.coffee Normal file
View file

@ -0,0 +1,100 @@
update_ways = (ways) ->
content = ""
for way in ways
if undum.game.situations[way]?
content += way_to(undum.game.situations[way].title, way)
$("#ways").html(content)
# Сюжет игры:
# герой находит коробку, коробка закрыта
# сломать коробку = шум
# открыть коробку - нужен код
# когда он хочет уйти, за дверью кто-то стоит - это другой вор, который оглушил Петю
# его можно убрать при помощи Чёрного Телефона
situation "living-room",
title: "Living room"
before: () ->
if not $(".ways h2").is(':visible')
$(".ways h2").fadeIn()
update_ways(this.ways)
ways: ["bedroom", "kitchen"]
content: """
Ronald is standing in a dark room with a big #{textlink("window.", "window")}
The walls are covered with a dingy rose wallpaper.
The room is very unnerving.
Was it actually her home?
On a coffee table lies an opened envelope.
#{textlink("A book stand", "bookcase")} is hanging above #{textlink("a television set.", "tv")}
Oh, and the door Ronald came into the apartment is there, too.
"""
writers:
window: "The moon is full today. It illuminates the flat, makes the things stand out in some weird angles."
bookcase: """
Either Anastacia has a very conflicting taste in books, or she has no taste at all. Let's see...
#{textlink("“Master and Margarita”,", "bulgakov")}
#{textlink("“Breakfast at Tiffany's”,", "tiffanys")}
#{textlink("The Soviet Encyclopedic Dictionary,", "dictionary")}
#{textlink("“Grey”,", "grey")}
#{textlink("”Also sprach Zarathustra”,", "zaratustra")}
#{textlink("”Poker with sharks”,", "dontzova")}
#{textlink("”Classic sauce. Culinary collection”", "culinary")}
and #{textlink("The Bible.", "bible")}
"""
bulgakov: """
Master and Margarita by Mikhail Bulgakov.
A famous urban fantasy satire of Soviet nineteen-thirties.
This is a cheap paperback edition, she read this at least a couple of times.
The pages have dog's ears, and coffee stains too.
Ronald can even see a hint of lipstick smearing the episode of Satan's dark magic variety show performance.
"""
tiffanys: """
This book looks scrawny wedged between the fat dictionary and an epic novel.
It looks rather new, the pages are still white and straight.
Maybe she didn't catch the right moment to read this.. or maybe just forgot about it.
"""
dictionary: "A big fat dictionary of everything, issued in 1989. Nobody reads every page of these. Ronald doubts Anastacia got to read at least one page."
grey: "Fifty Shades of Grey, an unnecessary remake. Now from a psychopath's point of view. And she actually read that."
zaratustra: (character, system) ->
money(character, system, 20000)
"""
Nietsche's four-part novel about The Man, The Superman and everything in-between.
It's surprisingly worn down.
She took this book out a lot.
Was she secretly a philosophy nut?
An Übermensch dreamer?
No, of course not.
Ronald opens the book and finds a stash of money inside.
"""
dontzova: """
An "ironic detective" by Daria Dontzova about Evlampia Romanova, an amateur detective.
The heroine (nicknamed as Lamp) plays harp and solves murders.
It's a trash book filled with blatant product placement.
"""
culinary: "An old culinary book. Nothing about it."
bible: "An Orthodox Christian Bible, Old Testament. A decent hardcover edition. Bookmarked at the Sodom episode."
tv: "An expensive 40-something inch TV standing on a stylish black stand. The room looks kinda small for that monster."
situation "bedroom",
before: () ->
update_ways(this.ways)
title: "Bedroom"
ways: ["living-room", "kitchen"]
content: """
Bedroom here
"""
situation "kitchen",
before: () ->
update_ways(this.ways)
title: "Kitchen"
ways: ["living-room", "bedroom"]
content: """
Bedroom here
"""

78
html/index.html Normal file
View file

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>The Black Phone</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div id="page" class="container">
<div class="row">
<div id="title" class="title">
<div class="label">
<h1>The Black Phone</h1>
<noscript>
<p class="noscript_message">This game requires Javascript.</p>
</noscript>
<p class="click_message">click to begin</p>
</div>
</div>
</div>
<div id="content_wrapper" class="row">
<div id="content">
</div>
<a name="end_of_content"></a>
</div>
<div id="tools_wrapper" class="row">
<div id="character_panel" class="tools">
<div id="character">
<div id="character_text">
<div id="character_text_content"></div>
</div>
<div id="qualities"></div>
</div>
</div>
<div class='ways'>
<h2>Other rooms</h2>
<div id="ways"></div>
</div>
<div class='buttons'>
<button id="save">Save</button><button id="erase">Restart</button>
</div>
</div> <!-- End of div.tools_wrapper -->
<div class="row">
<div id="legal">
<div id="footleft">
<p>The game was written by <b><a href="http://en.oreolek.ru/" target="_blank">Oreolek.</a></b></p>
<p>Approximate play time: four minutes.</p>
<p>Written using <a href="http://undum.com" target="_blank">Undum</a> and <a href="http://sequitur.github.io/raconteur/" target="_blank">Raconteur</a>.</p>
</div>
<div id="footright">
<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a>
</div>
</div>
</div>
</div> <!-- End of div.page -->
<div id="ui_library">
<div id="quality" class="quality">
<span class="name" data-attr="name"></span>
<span class="value" data-attr="value"></span>
</div>
<div id="quality_group" class="quality_group">
<h2 data-attr="title"></h2>
<div class="qualities_in_group">
</div>
</div>
<hr id="turn_separator">
</div>
<div id="content_library"></div>
<script type="text/javascript" src="game/undum.js"></script>
<script type="text/javascript" src="game/bundle.js"></script>
</body>
</html>

29
package.json Normal file
View file

@ -0,0 +1,29 @@
{
"dependencies": {
"undum": "git://github.com/oreolek/undum-commonjs#commonjs",
"raconteur": "git://github.com/sequitur/raconteur.git#stable",
"jquery": "^2.1.3",
"markdown-it": "^4.1.0",
"color": "^0.10.1"
},
"private": true,
"devDependencies": {
"babelify": "^6.0.2",
"browser-sync": "^2.6.0",
"browserify": "^9.0.8",
"browserify-shim": "^3.8.8",
"coffeeify": "^1.0.0",
"gulp": "^3.8.11",
"gulp-gzip": "^1.1.0",
"gulp-less": "^3.0.2",
"gulp-minify-css": "^1.0.0",
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.4",
"gulp-zip": "^3.0.2",
"lodash": "^3.6.0",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"watchify": "^3.1.0",
"shortid": "^2.2.4"
}
}

19
sass/_mixins.scss Normal file
View file

@ -0,0 +1,19 @@
@mixin halfcolumn() {
@include make-col();
@media (min-width: breakpoint-min(sm)) {
@include make-col-span(6);
}
@media (max-width: breakpoint-max(xs)) {
@include make-col-span(12);
}
}
@mixin col($sm-width, $xs-width) {
@include make-col;
@media (min-width: breakpoint-min(sm)) {
@include make-col-span($sm-width);
}
@media (max-width: breakpoint-max(xs)) {
@include make-col-span($xs-width);
}
}

14
sass/_variables.scss Normal file
View file

@ -0,0 +1,14 @@
$font-family-sans-serif: 'PT Sans','Open Sans',"Helvetica Neue", Helvetica, Arial, sans-serif;
$headings-font-family: "PT Sans Caption",$font-family-sans-serif;
// palette: http://www.colourlovers.com/palette/292482/Terra
$body-bg: #031634;
$body-color: #E9DDCB;
$link-color: #CDB380;
$btn-bg: #036564;
$btn-color: $body-color;
$secondary-bg: #033649;
$waycolor: $link-color;
$text_background: $body-bg; // can be btn-bg

1
sass/bootstrap Submodule

@ -0,0 +1 @@
Subproject commit 699c6bb383d0c0179ff0b895ce0520b005db28b1

230
sass/main.scss Normal file
View file

@ -0,0 +1,230 @@
@import "mixins";
@import "variables";
// Bootstrap v4 stripped core
@import "bootstrap/scss/variables";
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/normalize";
@import "bootstrap/scss/print";
@import "bootstrap/scss/reboot";
@import "bootstrap/scss/type";
@import "bootstrap/scss/images";
@import "bootstrap/scss/grid";
@import "bootstrap/scss/buttons";
@import "bootstrap/scss/responsive-embed";
@import "bootstrap/scss/utilities";
body {
overflow-y: scroll;
overflow-x: hidden;
background: $body-bg;
}
// The title block
.title {
margin-top: 2em;
@include col(10,12);
@media (min-width: breakpoint-min(sm)) {
@include make-col-offset(1);
}
cursor: pointer; // Until we click to start.
.label {
overflow: hidden;
margin: auto;
max-width: 18em;
position: relative;
text-align: center;
}
.subtitle {
font-size: smaller;
color: #aaa;
}
h1,
h2,
h3 {
//color: rgba(33,17,0,0.9);
text-shadow: rgba(255,255,255,0.5) 2px 2px 2px,
rgba(0,0,0,0.1) -1px -1px 2px;
}
.warnings {
font-size: small;
font-style: italic;
p {
margin-bottom: 1em;
}
}
.click_message {
display: none;
font-size: 0.9em;
font-style: italic;
text-align: center;
color: #987;
}
.noscript_message {
left: 0;
right: 0;
bottom: 0;
position: absolute;
font-size: 0.9em;
font-style: italic;
text-align: center;
color: #943;
}
}
#tools_wrapper {
display: none; // Shown by Javascript
/*
.tools {
background: $secondary-bg;
border-radius: 5px;
padding: 0.5em;
@include col(4, 5);
@media (min-width: breakpoint-min(sm)) {
@include make-col-offset(1);
}
}
*/
.ways {
h2 {
display: none; // Shown by Javascript
}
padding: 0.5em;
// @include col(4, 5);
@include col(9, 10);
@media (min-width: breakpoint-min(sm)) {
@include make-col-offset(1);
}
}
.buttons {
@include col(1, 2);
button {
@extend .btn;
@include button-variant($btn-color, $btn-bg, $btn-color);
margin-bottom: 1em;
}
}
}
#content_wrapper {
display: none; // Shown by Javascript
background: $text_background;
border-radius: 5px;
}
#content {
@include col(10, 12);
@media (min-width: breakpoint-min(sm)) {
@include make-col-offset(1);
}
padding: 1em;
ul {
margin: 0;
padding: 0 0 0 1em;
}
ul.options {
padding: 0;
text-align: center;
margin-top: 0.5em;
margin-bottom: 0.7em;
list-style-type: none;
border-radius: 4px;
li {
padding: 0.5em;
}
li:hover {
cursor: pointer;
}
li:last-child {
border-bottom: none;
}
}
section {
border-top: 1px dashed #bbb;
}
.situation-start {
border-top: none;
}
img.right {
float: right;
margin: 1.1em 0 1.1em 1.1em;
}
img.left {
float: left;
margin: 1.1em 1.1em 1.1em 0;
}
h3 {
text-align: center;
}
}
#legal {
@include col(10,12);
@media (min-width: breakpoint-min(sm)) {
@include make-col-offset(1);
}
margin-top: 1em;
color: #654;
font-size: smaller;
display: none; // Shown by Javascript
#footleft {
@include make-col();
@media (min-width: breakpoint-min(sm)) {
@include make-col-span(10);
}
@media (max-width: breakpoint-max(xs)) {
@include make-col-span(12);
}
}
#footright {
@include make-col();
@media (min-width: breakpoint-min(sm)) {
@include make-col-span(2);
}
@media (max-width: breakpoint-max(xs)) {
@include make-col-span(12);
margin-bottom: 1em;
}
}
}
#content_library,
#ui_library,
#menu {
display: none;
}
.controls {
display: none;
@include make-col();
@include make-col-span(12);
@media (max-width: breakpoint-max(xs)) {
margin-bottom: 1em;
}
#clip {
@include halfcolumn();
text-align: center;
img {
max-height: 50px;
}
}
.volume {
@include halfcolumn();
}
}
.way {
color: $waycolor;
margin-right: 1em;
}
.cycle {
color: darkgreen;
border-bottom: darkgreen dashed 1px;
}
ul.options {
border: 1px solid #876;
li {
border-bottom: 1px solid #876;
}
li:hover {
background-color: rgba(153,136,119,0.2);
}
}
#legal {
.muted {
color: grey;
}
}