Name
, Bio
description fields and counter fields.PATH
, GOROOT
, GOPATH
, APPENGINE_DEV_APPSERVER
) are APPENGINE_DEV_APPSERVER
, for this you should see the goapp
command in the goapp
. Looking ahead, Iβll say that to upload a simple project to the GAE server and start it, you need to run the goapp deploy
command in the project directory. She will ask you for the email of the google account on which the project should be located. It is important that the project name matches on app.yaml and on the site. But in this project modules are used and the loading process is somewhat different.Id
penguin Id
passed as a POST parameter. The server does not return anything in response, but you can, if you want, add the βOKβ line to the response or another signal of successful operation. PenguinDaycareSimulatorServer/ βββ default/ β βββ app.go β βββ default.yaml β βββ penguins.json βββ static/ β βββ favicon.ico β βββ static.yaml βββ dispatch.yaml
default
and static
are modules. The project for GAE can be divided into modules, and can work without them. In this case, only three files are app.yaml
: app.yaml
, app/app.go
and penguins.json
. Initially, it was in my project (you can see the first commit on GitHub), but I wanted to add the max_concurrent_requests
setting, which is responsible for how many simultaneous requests can be processed by one instance of your application. The default value is only 10. Go is clearly capable of more. The maximum value is 500. If the load grows and this value is exceeded, additional copies of your application are launched and the load is distributed between them. If you want to fit only in free quotas for GAE, then the use of this setting is highly desirable. If the application does not cope with such a load, then lower this value and switch to paid billing.static
is a very simple module that one could do without (if it were not for the GAE constraint above), its task is only to give a favicon.ico
file statically.default
is the main module that does all the work.*.yaml
files are settings and descriptions. One for each module and one dispatch.yaml
file, which describes which URLs which module handles which URLs. application: penguin-daycare-simulator dispatch: - url: "*/favicon.ico" module: static - url: "*/" module: default
application: penguin-daycare-simulator module: static version: 1 runtime: python27 api_version: 1 threadsafe: true handlers: - url: /favicon.ico static_files: favicon.ico upload: favicon.ico
application: penguin-daycare-simulator module: default version: 1 runtime: go api_version: go1 automatic_scaling: max_concurrent_requests: 500 handlers: - url: /.* script: _go_app
static.yaml
runtime Python is specified, not Go. This is done because GAE swears if you try to upload a module to Go without the Go files themselves. However, he does not swear in Python and PHP in this situation.handlers
in default.yaml
indicate which executable files are handled by specific URLs. In our case, app.go handles all incoming requests (taking into account dispatch.yaml
). Description URL is very flexible, uses regular expressions. However, if for Python and PHP you can use different files to process different URLs within a single module, then for Go it should be one single file, which is designated as "_go_app". Further, already inside the program on Go, you can select handlers for different URLs and split the entire application into several files, if necessary.penguins.json
is a JSON file containing the names and descriptions of all the penguins used. [ {"id": "1", "name": "Tux", "bio": "Beloved Linux mascot" }, {"id": "2", "name": "Skipper", "bio": "Small combat squad leader" }, {"id": "3", "name": "Lolo", "bio": "Russian adventurer" }, {"id": "4", "name": "Gunter", "bio": "The darkest character in Adventure Time" }, {"id": "5", "name": "The Penguin", "bio": "Na, na, na, na, na, na, na, na, na, na... The Penguin! " } ]
app.go
- the heart of the entire application. It is convenient to look at the full listing immediately on GitHub - app.go. package app . import (...) : Id, , , . type penguin struct {...} () . var penguins []penguin . type penguinEntity struct {...} . func init() {...} penguins.json penguins. func loadPenguinsJson() {...} / - . func rootHandler(w http.ResponseWriter, r *http.Request) {...} /penguins - JSON. func penguinsHandler(w http.ResponseWriter, r *http.Request) {...} /stat/visit - . func visitHandler(w http.ResponseWriter, r *http.Request) {...} /stat/fish - . func fishHandler(w http.ResponseWriter, r *http.Request) {...} /stat/bellyrub - . func bellyrubHandler(w http.ResponseWriter, r *http.Request) {...}
penguinsHandler()
serializes the penguins slice into JSON format json.Marshal()
function and gives it to clients via fmt.Fprint()
.visitHandler()
, fishHandler()
, bellyrubHandler()
act according to the same logic - we take the penguin from the database, increment the corresponding parameter by one and write it back to the database. The database - Datastore - is not SQL compatible, that is, it is a NoSQL solution. The description of her work is worthy of a separate article.lastUpdateTime
- the timestamp of the last update of the penguins
slice. And with each query /penguins
I call the updatePenguinsStatistics()
function, which checks whether the cache has expired and updates the counter readings for each penguin in the penguins
slice.updateHandler()
handler.penguins
slice from possible simultaneous recording or reading while writing. RWMutex
is used for this - read or write mutex. Its use is more efficient than simple Mutex
. goapp deploy default/default.yaml goapp deploy static/static.yaml appcfg.py update_dispatch .
goapp deploy default/default.yaml
. PenguinDaycareSimulator/ βββ images/ β βββ penguins/ β β βββ 1.png β β βββ 1@2x.png β β βββ 2.png β β βββ 2@2x.png β β βββ 3.png β β βββ 3@2x.png β β βββ 4.png β β βββ 4@2x.png β β βββ 5.png β β βββ 5@2x.png β βββ background.jpg β βββ background@2x.jpg β βββ button-over.png β βββ button-over@2x.png β βββ button.png β βββ button@2x.png β βββ dot-off.png β βββ dot-off@2x.png β βββ dot.png β βββ dot@2x.png β βββ fish.png β βββ fish@2x.png β βββ hand.png β βββ hand@2x.png β βββ popup.png β βββ popup@2x.png βββ lib/ β βββ api.lua β βββ app.lua β βββ utils.lua βββ scenes/ β βββ choose.lua β βββ menu.lua β βββ penguin.lua βββ Default-568h@2x.png βββ Icon-60.png βββ Icon-60@2x.png βββ Icon-72.png βββ Icon-72@2x.png βββ Icon-76.png βββ Icon-76@2x.png βββ Icon-Small-40.png βββ Icon-Small-40@2x.png βββ Icon-Small-50.png βββ Icon-Small-50@2x.png βββ Icon-Small.png βββ Icon-Small@2x.png βββ Icon-hdpi.png βββ Icon-ldpi.png βββ Icon-mdpi.png βββ Icon-ouya.png βββ Icon-xhdpi.png βββ Icon-xxhdpi.png βββ Icon.png βββ Icon@2x.png βββ build.settings βββ config.lua βββ main.lua
config.lua
, build.settings
- project configuration files for Corona SDK. Specify a portrait or landscape view has the application, the reference screen resolution, zoom method and other different settings. If the Corona SDK is new to you, you can ignore these files for now.Default-568h@2x.png
for iPhone 5 to work correctly. Inside the images / directory there are regular files and their doubled HD versions @2x
. Now, in principle, it is already possible not to support devices with screens like the iPhone 3GS, their percentage is very small, but nonetheless non-zero. To fully support the iPad Retina, you will already need @4x
files and a line in config.lua
, but most games work fine.main.lua
file, the necessary libraries are connected to it, some variables are declared and a transition to the stage with the Enter the Daycare button takes place. All scenes (screens) of the application are stored in different files and collected in the scenes/
directory, and I placed all user libraries in lib/
. The developer is free to place these files as he wants, I prefer so.lib/
contains app.lua
and utils.lua
- together this is my collection of useful functions for working with the Corona SDK. In app.lua
, convenient wrappers are implemented over the standard Corona SDK functions for displaying images, text, buttons, etc.main.lua
to scenes/menu.lua
done through the line storyboard.gotoScene('scenes.menu')
menu.lua
. function scene:createScene (event) local group = self.view app.newText{g = group, text = 'Penguin Daycare', size = 32, x = _CX, y = _CY - 150} app.newText{g = group, text = 'Simulator', size = 32, x = _CX, y = _CY - 110} local pleaseWait = app.newText{g = group, text = 'Please Wait', size = 16, x = _CX, y = _CY} local button = app.newButton{g = group, x = _CX, y = _CY, text = 'Enter the Daycare', onRelease = function() storyboard.gotoScene('scenes.choose', {effect = 'slideLeft', time = app.duration}) end} button.isVisible = false app.api:getPenguins(function() pleaseWait.isVisible = false button.isVisible = true end) end
app.api:getPenguins()
function, with a callback function as its argument.choose.lua
. function scene:createScene(event) local group = self.view self.backButton = app.newButton{g = group, x = _L + 10, y = _T + 10, w = 48, h = 32, rp = 'TopLeft', text = 'Back', fontSize = 14, onRelease = function() storyboard.gotoScene('scenes.menu', {effect = 'slideRight', time = app.duration}) end} local function gotoPenguin(ind) storyboard.gotoScene('scenes.penguin', {effect = 'slideLeft', time = app.duration, params = ind}) end local slideView = newSlideView{g = group, x = 0, y = _CY, dots_y = 180, onRelease = gotoPenguin} for i = 1, #app.api.penguins do local p = app.api.penguins[i] local slide = display.newGroup() app.newImage('images/popup.png', {g = slide, w = 300, h = 335}) app.newImage('images/penguins/' .. p.id .. '.png', {g = slide, w = 200, h = 256}) app.newText{g = slide, x = 0, y = -140, text = p.name, size = 18, color = 'white'} app.newText{g = slide, x = 0, y = 140, text = p.bio, size = 14, color = 'white', w = 220, align = 'center'} slideView:addSlide(slide) end slideView:makeDots() slideView:gotoSlide(1) end
newSlideView()
is a function that creates a simple widget with which you can scroll through slides with penguins. The code for this widget is located right at choose.lua
at the beginning of the file.display.loadRemoteImage()
function or a lower-level network.download()
.gotoPenguin()
function is gotoPenguin()
, which receives the number (not Id
) of the penguin in the array (table) of all the penguins obtained. This function makes the transition to the final scene of penguin.lua
, passing the same argument to this scene. function scene:createScene(event) local group = self.view local background = app.newImage('images/background.jpg', {g = group, w = 384, h = 640, x = _CX, y = _CY}) self.backButton = app.newButton{g = group, x = _L + 10, y = _T + 10, w = 48, h = 32, rp = 'TopLeft', text = 'Back', fontSize = 14, onRelease = function() storyboard.gotoScene('scenes.choose', {effect = 'slideRight', time = app.duration}) end} local ind = event.params local p = app.api.penguins[ind] local visitsLabel = app.newText{g = group, x = _CX, y = _T + 20, text = 'Visits: ' .. p.visit_count, size = 18, color = 'white'} local fishLabel = app.newText{g = group, x = _CX, y = _T + 40, text = 'Fish: ' .. p.fish_count, size = 18, color = 'white'} local bellyrubsLabel = app.newText{g = group, x = _CX, y = _T + 60, text = 'Belly rubs: ' .. p.bellyrub_count, size = 18, color = 'white'} local penguin = app.newImage('images/penguins/' .. p.id .. '.png', {g = group, w = 200, h = 256, x = _CX, y = _CY - 25}) app.newButton{g = group, x = _CX - 80, y = _B - 50, w = 128, h = 48, text = 'Fish', fontSize = 14, onRelease = function() local fish = app.newImage('images/fish.png', {g = group, x = penguin.x, y = penguin.y + 200, w = 512, h = 188}) fish.alpha = 0.8 transition.to(fish, {time = 400, alpha = 1, y = penguin.y, xScale = 0.1, yScale = 0.1, transition = easing.outExpo, onComplete = function(obj) transition.to(fish, {time = 400, alpha = 0, onComplete = function(obj) display.remove(obj) end}) end}) app.api:sendFish(p.id) p.fish_count = p.fish_count + 1 fishLabel:setText('Fish: ' .. p.fish_count) end} app.newButton{g = group, x = _CX + 80, y = _B - 50, w = 128, h = 48, text = 'Belly rub', fontSize = 14, onRelease = function() local hand = app.newImage('images/hand.png', {g = group, x = penguin.x - 40, y = penguin.y + 30, w = 80, h = 80, rp = 'TopLeft'}) transition.to(hand, {time = 1200, x = penguin.x + 40, transition = easing.swing3(easing.outQuad), onComplete = function(obj) display.remove(obj) end}) app.api:sendBellyrub(p.id) p.bellyrub_count = p.bellyrub_count + 1 bellyrubsLabel:setText('Belly rubs: ' .. p.bellyrub_count) end} app.api:sendVisit(p.id) p.visit_count = p.visit_count + 1 visitsLabel:setText('Visits: ' .. p.visit_count) end
penguin.lua
, the background image, the image of the selected penguin is loaded, several text labels and two action buttons are displayed. When you click on them, the action is animated and the request is sent to the server through the app.api:sendFish()
and app.api:sendBellyrub()
. And app.api:sendVisit()
is called immediately after creating the scene. After calling each of these functions, the corresponding text labels are updated, even if there is no Internet. This can be corrected by entering a check for receiving a response from the server for each request and providing callback functions.lib/api.lua
. local _M = {} local app = require('lib.app') _M.hostname = 'http://penguin-daycare-simulator.appspot.com' function _M:getPenguins(callback) local url = '/penguins#' .. math.random(1000, 9999) network.request(self.hostname .. url , 'GET', function (event) if not event.isError then local response = json.decode(event.response) if response then self.penguins = response callback() end end end) end function _M:sendVisit(id) local url = '/stat/visit' local request = {body = 'id=' .. id} network.request(self.hostname .. url , 'POST', function (event) if event.isError then app.alert('Network error') end end, request) end function _M:sendFish(id) local url = '/stat/fish' local request = {body = 'id=' .. id} network.request(self.hostname .. url , 'POST', function (event) if event.isError then app.alert('Network error') end end, request) end function _M:sendBellyrub(id) local url = '/stat/bellyrub' local request = {body = 'id=' .. id} network.request(self.hostname .. url , 'POST', function (event) if event.isError then app.alert('Network error') end end, request) end return _M
getPenguins()
, the response from the server is converted from JSON format to an array (table) by the json.decode()
function and placed in the field (property) of the module.Source: https://habr.com/ru/post/220031/
All Articles