Memory leak caused by MobileAds.registerWebView - google-mobile-ads

I add MobileAds.registerWebView (webView) in activity onCreate; However, a memory leak was found,
When I delete this code, I will not prompt for memory leak
How to solve this problem?
GC Root: Thread object
│
├─ Dk instance
│ Leaking: NO (PathClassLoader↓ is not leaking)
│ Thread name: 'CleanupReference'
│ ↓ Thread.contextClassLoader
├─ dalvik.system.PathClassLoader instance
│ Leaking: NO (zzbzz↓ is not leaking and A ClassLoader is never leaking)
│ ↓ ClassLoader.runtimeInternalObjects
├─ java.lang.Object[] array
│ Leaking: NO (zzbzz↓ is not leaking)
│ ↓ Object[2266]
├─ com.google.android.gms.internal.ads.zzbzz class
│ Leaking: NO (a class is never leaking)
│ ↓ static zzbzz.zza
│ ~~~
├─ com.google.android.gms.internal.ads.zzcfk instance
│ Leaking: UNKNOWN
│ Retaining 16 B in 1 objects
│ ↓ zzasd.zza
│ ~~~
├─ com.google.android.gms.ads.nonagon.signalgeneration.ab instance
│ Leaking: UNKNOWN
│ Retaining 379.4 kB in 5156 objects
│ e instance of sg.just4fun.tgasdk.ui.HomeActivity with mDestroyed = true
│ ↓ ab.e
│ ~
╰→ xxx.xxx.WebViewActivity instance
​ Leaking: YES (ObjectWatcher was watching this because xxx.xxx.WebViewActivity received Activity#onDestroy() callback and
​ Activity#mDestroyed is true)
​ Retaining 376.6 kB in 5078 objects
​ key = 9500b00e-35f4-476d-bb2e-836c21f4c80f
​ watchDurationMillis = 7971
​ retainedDurationMillis = 2971
​ mApplication instance of com.just4fun.tga.App
​ mBase instance of androidx.appcompat.view.ContextThemeWrapper

Related

Google Cloud Storage NodeJS multiple read requests loading too slow

how do you do?
I'm trying to figure out why some requests to my Images API (usually the last ones) are taking over than 1 minute to load. The first ones are basically instantaneous. Search all over the internet, but had no apropriate answer yet. I am using Google Cloud to storage the images and NodeJS at the server, who is providing the images as a bufferized writing head to the browser.
You can check what am I saying accessing the website (18 year old content):
https://divinasacompanhantes.com/
As you can see, some images just don't load properly. I'm worried because this website is expected to have thousands more profiles, all over the world.
I am using PM2 to handle the services at the server side (2GB available memory). Here's the table:
┌─────┬─────────────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
├─────┼─────────────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 7 │ ServiceAfiliado │ default │ 1.0.0 │ fork │ 31312 │ 20m │ 3 │ online │ 0% │ 55.9mb │ root │ disabled │
│ 0 │ ServiceAvaliacao │ default │ 1.0.0 │ fork │ 31249 │ 20m │ 3 │ online │ 0% │ 55.2mb │ root │ disabled │
│ 8 │ ServiceBlog │ default │ 1.0.0 │ fork │ 31330 │ 20m │ 3 │ online │ 0% │ 61.1mb │ root │ disabled │
│ 1 │ ServiceChat │ default │ 1.0.0 │ fork │ 31256 │ 20m │ 3 │ online │ 0% │ 57.3mb │ root │ disabled │
│ 9 │ ServiceConfig │ default │ 1.0.0 │ fork │ 31337 │ 20m │ 3 │ online │ 0% │ 56.2mb │ root │ disabled │
│ 10 │ ServiceImage │ default │ 1.0.0 │ fork │ 31904 │ 0s │ 13 │ online │ 0% │ 19.1mb │ root │ disabled │
│ 2 │ ServiceLead │ default │ 1.0.0 │ fork │ 31269 │ 20m │ 3 │ online │ 0% │ 54.8mb │ root │ disabled │
│ 3 │ ServiceMail │ default │ 1.0.0 │ fork │ 31276 │ 20m │ 3 │ online │ 0% │ 43.3mb │ root │ disabled │
│ 4 │ ServicePagamento │ default │ 1.0.0 │ fork │ 31289 │ 20m │ 3 │ online │ 0% │ 42.5mb │ root │ disabled │
│ 5 │ ServiceParceiro │ default │ 1.0.0 │ fork │ 31296 │ 20m │ 3 │ online │ 0% │ 60.1mb │ root │ disabled │
│ 6 │ ServicePerfil │ default │ 1.0.0 │ fork │ 31309 │ 20m │ 3 │ online │ 0% │ 69.7mb │ root │ disabled │
└─────┴─────────────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
The route handling this specific request:
router.get('/image/:imageId', async function (req, res) {
try {
let imageId = req.param('imageId')
let returnImage = await cloudController.getImageFromBucket('fotos_perfil', imageId)
res.writeHead(200, {'Content-Type': 'image/jpg'});
returnImage.on('data', (data) => {
res.write(data)
})
returnImage.on('error', (error) => {
res.status(400).send('Erro lendo a imagem')
console.error(error)
})
returnImage.on('end', () => {
res.end()
})
} catch (err) {
res.status(500).send('Internal Server Error')
}
})
And the controller associated:
async function getImageFromBucket(bucket, imageId) {
return new Promise((resolve, reject) => {
try {
let imageInfo = storage.bucket(bucket).file(imageId).createReadStream()
resolve(imageInfo)
} catch (e) {
reject(e)
}
})
}
Can anyone provide me some ideas to solve this? I've read the official Google documentation and the tip is to use fast-crc32c, but only. No clues on how to configure...

ts-jest disable babel, babel still appears to be running

So the docs say it should not be; but I'm seeing this output.
➤ YN0000: [#bb/graph]: [BABEL] Note: The code generator has deoptimised the styling of /home/runner/work/services/services/.pnp.js as it exceeds the max of 500KB.
> yarn info -A '*babel-*' # services -> feature/RS2-1243-logging $ !
├─ babel-jest#npm:26.5.2
│ ├─ Instances: 1
│ ├─ Version: 26.5.2
│ │
│ └─ Dependencies
│ ├─ #jest/transform#npm:^26.5.2 → npm:26.5.2
│ ├─ #jest/types#npm:^26.5.2 → npm:26.5.2
│ ├─ #types/babel__core#npm:^7.1.7 → npm:7.1.9
│ ├─ babel-plugin-istanbul#npm:^6.0.0 → npm:6.0.0
│ ├─ babel-preset-jest#npm:^26.5.0 → npm:26.5.0
│ ├─ chalk#npm:^4.0.0 → npm:4.1.0
│ ├─ graceful-fs#npm:^4.2.4 → npm:4.2.4
│ └─ slash#npm:^3.0.0 → npm:3.0.0
│
├─ babel-plugin-dynamic-import-node#npm:2.3.3
│ ├─ Version: 2.3.3
│ │
│ └─ Dependencies
│ └─ object.assign#npm:^4.1.0 → npm:4.1.0
│
├─ babel-plugin-istanbul#npm:6.0.0
│ ├─ Version: 6.0.0
│ │
│ └─ Dependencies
│ ├─ #babel/helper-plugin-utils#npm:^7.0.0 → npm:7.10.4
│ ├─ #istanbuljs/load-nyc-config#npm:^1.0.0 → npm:1.1.0
│ ├─ #istanbuljs/schema#npm:^0.1.2 → npm:0.1.2
│ ├─ istanbul-lib-instrument#npm:^4.0.0 → npm:4.0.3
│ └─ test-exclude#npm:^6.0.0 → npm:6.0.0
│
├─ babel-plugin-jest-hoist#npm:26.5.0
│ ├─ Version: 26.5.0
│ │
│ └─ Dependencies
│ ├─ #babel/template#npm:^7.3.3 → npm:7.10.4
│ ├─ #babel/types#npm:^7.3.3 → npm:7.11.5
│ ├─ #types/babel__core#npm:^7.0.0 → npm:7.1.9
│ └─ #types/babel__traverse#npm:^7.0.6 → npm:7.0.13
│
├─ babel-plugin-syntax-trailing-function-commas#npm:7.0.0-beta.0
│ └─ Version: 7.0.0-beta.0
│
├─ babel-preset-current-node-syntax#npm:0.1.3
│ ├─ Instances: 1
│ ├─ Version: 0.1.3
│ │
│ └─ Dependencies
│ ├─ #babel/plugin-syntax-async-generators#npm:^7.8.4 → npm:7.8.4
│ ├─ #babel/plugin-syntax-bigint#npm:^7.8.3 → npm:7.8.3
│ ├─ #babel/plugin-syntax-class-properties#npm:^7.8.3 → npm:7.10.4
│ ├─ #babel/plugin-syntax-import-meta#npm:^7.8.3 → npm:7.10.4
│ ├─ #babel/plugin-syntax-json-strings#npm:^7.8.3 → npm:7.8.3
│ ├─ #babel/plugin-syntax-logical-assignment-operators#npm:^7.8.3 → npm:7.10.4
│ ├─ #babel/plugin-syntax-nullish-coalescing-operator#npm:^7.8.3 → npm:7.8.3
│ ├─ #babel/plugin-syntax-numeric-separator#npm:^7.8.3 → npm:7.10.4
│ ├─ #babel/plugin-syntax-object-rest-spread#npm:^7.8.3 → npm:7.8.3
│ ├─ #babel/plugin-syntax-optional-catch-binding#npm:^7.8.3 → npm:7.8.3
│ └─ #babel/plugin-syntax-optional-chaining#npm:^7.8.3 → npm:7.8.3
│
├─ babel-preset-fbjs#npm:3.3.0
│ ├─ Instances: 1
│ ├─ Version: 3.3.0
│ │
│ └─ Dependencies
│ ├─ #babel/plugin-proposal-class-properties#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-proposal-object-rest-spread#npm:^7.0.0 → npm:7.11.0
│ ├─ #babel/plugin-syntax-class-properties#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-syntax-flow#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-syntax-jsx#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-syntax-object-rest-spread#npm:^7.0.0 → npm:7.8.3
│ ├─ #babel/plugin-transform-arrow-functions#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-transform-block-scoped-functions#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-transform-block-scoping#npm:^7.0.0 → npm:7.11.1
│ ├─ #babel/plugin-transform-classes#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-transform-computed-properties#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-transform-destructuring#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-transform-flow-strip-types#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-transform-for-of#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-transform-function-name#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-transform-literals#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-transform-member-expression-literals#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-transform-modules-commonjs#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-transform-object-super#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-transform-parameters#npm:^7.0.0 → npm:7.10.5
│ ├─ #babel/plugin-transform-property-literals#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-transform-react-display-name#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-transform-react-jsx#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-transform-shorthand-properties#npm:^7.0.0 → npm:7.10.4
│ ├─ #babel/plugin-transform-spread#npm:^7.0.0 → npm:7.11.0
│ ├─ #babel/plugin-transform-template-literals#npm:^7.0.0 → npm:7.10.5
│ └─ babel-plugin-syntax-trailing-function-commas#npm:^7.0.0-beta.0 → npm:7.0.0-beta.0
│
└─ babel-preset-jest#npm:26.5.0
├─ Instances: 1
├─ Version: 26.5.0
│
└─ Dependencies
├─ babel-plugin-jest-hoist#npm:^26.5.0 → npm:26.5.0
└─ babel-preset-current-node-syntax#npm:^0.1.3 → npm:0.1.3
> yarn why -R 'babel-jest' # services -> feature/RS2-1243-logging $ !
├─ #bb/apollo-test-util#workspace:app-lib/graph/packages/apollo-test-util
│ └─ jest#npm:26.5.2 (via npm:^26.5.2)
│ ├─ #jest/core#npm:26.5.2 (via npm:^26.5.2)
│ │ └─ jest-config#npm:26.5.2 (via npm:^26.5.2)
│ │ └─ babel-jest#npm:26.5.2 [dc086] (via npm:^26.5.2 [dc086])
│ └─ jest-cli#npm:26.5.2 (via npm:^26.5.2)
│ ├─ #jest/core#npm:26.5.2 (via npm:^26.5.2)
│ └─ jest-config#npm:26.5.2 (via npm:^26.5.2)
│
├─ #bb/app-lib#workspace:app-lib
│ └─ jest#npm:26.5.2 (via npm:^26.5.2)
│
├─ #bb/graph#workspace:app-lib/graph/packages/app
│ ├─ #bb/apollo-test-util#workspace:app-lib/graph/packages/apollo-test-util (via workspace:app-lib/graph/packages/apollo-test-util)
│ └─ jest#npm:26.5.2 (via npm:^26.5.2)
│
└─ #bb/test-apollo-configuration#workspace:app-lib/graph/packages/test-apollo-configuration
└─ #bb/apollo-test-util#workspace:app-lib/graph/packages/apollo-test-util (via workspace:app-lib/graph/packages/
is there any way with yarn2 I can just remove babel safely? or barring that completely prevent it from running?

Leak canary detects some memory leak on detach fragments

When I detach fragments from activity, Leak Canary detects some memory leaks inside androidx.core.widget.NestedScrollView. I have no observable in it and just a viewmodel binded to its databinding object.
ApplicationLeak(className=androidx.core.widget.NestedScrollView, leakTrace=
┬
.
.
.
│ ↓ MainActivity.controller
├─ com.ncapdevi.fragnav.FragNavController
│ Leaking: NO (ArrayList↓ is not leaking)
│ ↓ FragNavController.rootFragments
├─ java.util.ArrayList
│ Leaking: NO (Object[]↓ is not leaking)
│ ↓ ArrayList.elementData
├─ java.lang.Object[]
│ Leaking: NO (MoreFragment↓ is not leaking)
│ ↓ array Object[].[3]
├─ my.package.name.ui.main.more.MoreFragment
│ Leaking: NO (Fragment#mFragmentManager is not null)
│ Fragment.mTag=my.package.name.ui.main.more.MoreFragment4
│ ↓ MoreFragment.binding
│ ~~~~~~~
├─ my.package.name.databinding.FragmentMoreBindingImpl
│ Leaking: UNKNOWN
│ ↓ FragmentMoreBindingImpl.mRoot
│ ~~~~~
╰→ androidx.core.widget.NestedScrollView
Leaking: YES (ObjectWatcher was watching this)
mContext instance of my.package.name.ui.main.MainActivity with mDestroyed = false
View#mParent is null
View#mAttachInfo is null (view detached)
View.mWindowAttachCount = 1
key = 2262922d-06f8-4abb-ba7b-d276c6fe3082
watchDurationMillis = 1910
retainedDurationMillis = -1
, retainedHeapByteSize=7132)
MoreFragment is holding on to FragmentMoreBindingImpl (which itself holds NestedScrollView) after onDestroyView. You should clear the reference to the binding in Fragment.onDestroyView()

Spawning a new process on node on an headless Raspberry

I'm currently trying to spawn a process inside my node server to take a screenshot of the only screen attached to my raspberry with this command :
var scrot = childProcess.spawn(path.join(__dirname, "bin", "scrot", "scrot"), [options.output]);
This command work on my local machine but I get a code 2 response when I try to run it on my headless raspberry under Debian. I suspect it is because my node process is spawned at the beginning of the boot routine, before the x server is started.
The pstree command show me this :
systemd─┬─avahi-daemon───avahi-daemon
├─bluetoothd
├─cron
├─2*[dbus-daemon]
├─dbus-launch
├─dhcpcd
├─hciattach
├─login───startx───xinit─┬─Xorg───{InputThread}
│ └─openbox─┬─openbox-autosta───sh───chromium-browse─┬─ch+
│ │ ├─ch+
│ │ ├─{A+
│ │ ├─{B+
│ │ ├─{C+
│ │ ├─{C+
│ │ ├─{C+
│ │ ├─{C+
│ │ ├─{C+
│ │ ├─{D+
│ │ ├─{N+
│ │ ├─2*+
│ │ ├─3*+
│ │ ├─{T+
│ │ ├─7*+
│ │ ├─{c+
│ │ ├─{e+
│ │ ├─{g+
│ │ ├─{i+
│ │ ├─{r+
│ │ └─{s+
│ └─ssh-agent
├─node───9*[{node}]
Is there a way to add a child process to the x server context ?
Thanks for any help in advance,
C.

A simpler way to determine the winner in tic-tac-toe?

I model the tic-tac-toe game board this way:
one sig gameBoard {
cells: Row -> Col -> Mark -> Time
}
Mark is either X or O:
enum Mark { X, O }
Row and Col are simply sets:
sig Row {}{ #Row = 3}
sig Col {}{ #Col = 3}
There is a winner when:
there is a row with all X's or all O's, or
there is a col with all X's or all O's, or
there is a left-to-right diagonal with all X's or all O's, or
there is a right-to-left diagonal with all X's or all O's.
I express that with the following complex predicate. Is there a simpler way to express the winner?
pred winner [t: Time] {
some m: Mark |
some r: Row | all c: Col | board[r, c, t] = m
or
some c: Col| all r: Row | board[r, c, t] = m
or
board[first, first, t] = m and
board[first.next, first.next, t] = m and
board[first.next.next, first.next.next, t] = m
or
board[last,last, t] = m and
board[last.prev, last.prev, t] = m and
board[last.prev.prev,last.prev.prev, t] = m
}
Here is my complete tic-tac-toe model:
open util/ordering[Time]
open util/ordering[Row]
open util/ordering[Col]
/*
Structure:
1. The game is played on a 3x3 board.
2. There are two players, Player1 and Player2.
3. Players mark the game board with either X or O.
4. The game is played over a series of time steps.
*/
// 4. The game is played over a series of time steps.
sig Time {}
// 3. Players mark the game board with either X or O.
enum Mark { X, O }
// 2. There are two players, Player1 and Player2.
enum Player { Player1, Player2 }
// 1. The game is played on a ... board.
one sig gameBoard {
cells: Row -> Col -> Mark -> Time
}
// 1. ... on a 3x3 board.
sig Row {}{ #Row = 3}
sig Col {}{ #Col = 3}
/*
Constraints:
1. Each cell has at most one mark (X or O) at each time.
2. A win stops further marking.
3. When all cells are marked, there can be no additional marking.
4. Players alternate moves.
5. There is no interrupt in the play: If cells are empty at time t-1,
and there is no winner at time t-1, then there will be one
fewer empty cells at time t. If there is a winner at time t-1,
then there will be no change to the number of empty cells at
time t (per invariant 2).
6. Player1 marks cells O and Player2 marks cells X.
7. When there is a winner or when all cells are marked,
then the recording of "last player to move" is blank.
*/
// 1. Each cell has at most one mark (X or O) at each time.
pred Each_cell_has_at_most_one_mark {
no r: Row, c: Col, t: Time, disj m, m': Mark |
((r -> c -> m) in gameBoard.cells.t) and
((r -> c -> m') in gameBoard.cells.t)
}
// 2. A win stops further marking.
pred gameBoard_remains_unchanged_after_win {
all t: Time - first |
winner[t.prev] => gameBoard.cells.t = gameBoard.cells.(t.prev)
}
// 3. When all cells are marked, there can be no additional marking.
pred gameBoard_remains_unchanged_after_every_cell_is_marked {
all t: Time - first |
every_cell_is_marked[t.prev] => gameBoard.cells.t = gameBoard.cells.(t.prev)
}
// 4. Players alternate moves.
pred Players_alternately_move {
no t: Time - last, t': t.next |
(some LastPlayerToMove.person.t) and
(some LastPlayerToMove.person.t') and
(LastPlayerToMove.person.t = LastPlayerToMove.person.t')
}
// 5. There is no interrupt in the play: If cells are empty at time t-1,
// and there is no winner at time t-1, then there will be one
// fewer empty cells at time t. If there is a winner at time t-1,
// then there will be no change to the number of empty cells at
// time t (per invariant 2).
pred Progressively_fewer_empty_cells {
all t: Time - first |
not every_cell_is_marked[t.prev] and not winner[t.prev] =>
#empty_cells[t] < #empty_cells[t.prev]
}
// 6. Player1 marks cells O and Player2 marks cells X.
pred Players_mark_cells_appropriately {
all t: Time - first |
not every_cell_is_marked[t.prev] and not winner[t.prev] =>
let c = gameBoard.cells.t - gameBoard.cells.(t.prev) |
c[Row][Col] = X =>
(LastPlayerToMove.person.t = Player2)
else
(LastPlayerToMove.person.t = Player1)
}
// 7. When there is a winner or when all cells are marked,
// then the recording of "last player to move" is blank.
pred LastPlayerToMove_remains_unchanged_after_win_or_all_cells_marked {
all t: Time - first |
((every_cell_is_marked[t.prev]) or (winner[t.prev])) =>
no LastPlayerToMove.person.t
}
// This provides one place that you can call to
// have all the constraints enforced.
pred game_is_constrained_by_these_constraints {
Each_cell_has_at_most_one_mark
gameBoard_remains_unchanged_after_win
gameBoard_remains_unchanged_after_every_cell_is_marked
Players_alternately_move
Progressively_fewer_empty_cells
Players_mark_cells_appropriately
LastPlayerToMove_remains_unchanged_after_win_or_all_cells_marked
}
// Return the set of empty cells at time t.
// This is implemented using set subtraction.
// (Row -> Col) is the set of all possible combinations
// of row and col. Subtract from that the set
// of (row, col) pairs containing a mark at time t.
fun empty_cells[t: Time]: Row -> Col {
(Row -> Col) - gameBoard.cells.t.Mark
}
// Once the game board is completely marked,
// there won't be a "last player." Ditto for when
// there is a winner. That's why there "may" be
// a last player at time t. That is, there isn’t
// necessarily a player involved at every time step,
// i.e., there isn’t necessarily a (Player, Time) pair
// for every value of Time.
one sig LastPlayerToMove {
person: Player lone -> Time
}
// Return the mark (X or O) on board[r][c] at time t,
// or none if there is no mark.
fun board [r: Row, c: Col, t: Time]: lone Mark {
gameBoard.cells[r][c].t
}
// There is a winner when (a) there is a row
// with all X's or all O's, or (b) there is a col
// with all X's or all O's, or (c) there is a left-to-right
// diagonal with all X's or all O's, or (d) there is a
// right-to-left diagonal with all X's or all O's.
pred winner [t: Time] {
some m: Mark |
some r: Row | all c: Col | board[r, c, t] = m
or
some c: Col| all r: Row | board[r, c, t] = m
or
board[first, first, t] = m and
board[first.next, first.next, t] = m and
board[first.next.next, first.next.next, t] = m
or
board[last,last, t] = m and
board[last.prev, last.prev, t] = m and
board[last.prev.prev,last.prev.prev, t] = m
}
// Every call of the game board is marked when
// the set of cells with marks equals all combinations
// of (row, col)
pred every_cell_is_marked[t: Time] {
gameBoard.cells.t.Mark = (Row -> Col)
}
// Initially the game board has no cells.
// One of the players is first to play.
// The game is constrained by the invariants.
pred init [t: Time] {
no gameBoard.cells.t
one p: Player | LastPlayerToMove.person.t = p
game_is_constrained_by_these_constraints
}
pred doNothing [t: Time] {
gameBoard.cells.t = gameBoard.cells.(t.prev)
}
pred Play {
init[first]
all t: Time - first |
X.marked_on_gameboard_at_time[t]
or O.marked_on_gameboard_at_time[t]
or doNothing[t]
}
pred marked_on_gameboard_at_time [m: Mark, t: Time] {
some r: Row, c: Col {
gameBoard.cells.t = gameBoard.cells.(t.prev) +
{r': Row, c': Col, m': Mark | r' = r and c' = c and m' = m}
}
}
run Play for 3 but 12 Time
comment: You can run this whole markdown text in Alloy 5 Beta
TIC-TAC-TOE
We design this game around a Board. The Board is the state and we will use game rules encoded in predicates to
constrain the transitions to the next board.
Setup
Setup the game by defining the board size, the board and the players. The Board has a relative large number
of fields because that makes the trace output nice to read.
```alloy
open util/ordering[Board]
let N = 0+1+2
let highest = max[N]
sig Board {
cells : N -> N -> Cell,
move: Cell,
to : N->N,
won : Cell
}
enum Cell { _, X, O }
```
Winning
The game is won when there is a row, a colum, or a diagonal that holds the same player. We can encode this as follows:
```alloy
let rightdiag = { r,c : N | r = c }
let leftdiag = { r,c : N | c = highest.minus[r] }
pred won[b,b':Board ] {
some token : X + O {
let positions = b.cells.token {
some row : N | N = row.positions
or some column : N | N = positions.column
or rightdiag in positions
or leftdiag in positions
b'.won = token
}
}
}
```
Finished
The game is finished when a player won or there are no more free places.
```alloy
pred finished[ b,b' : Board ] {
not won[b,b'] implies {
b'.won = _
_ not in b'.cells[N][N]
}
b'.cells = b.cells
b'.move = _
b'.to = none -> none
}
```
Play
Plays should alternate between the players. That is why we keep the player in the previous board's move field
and check it is not us. We then constrain the board to have an empty position replaced with the player's
token.
```alloy
pred play[b, b' : Board ] {
b'.won=_
some token : X+O {
b.move != token
b'.move = token
some row,col : N {
b.cells[row][col] = _
b'.cells = b.cells - row->col->_ + row->col->token
b'.to = row->col
}
}
}
```
Trace
Then the only thing remaining is to setup the first board and ensure the game (the trace of Boards) is
played according to the rules.
```alloy
fact trace {
first.move = _
first.won = _
first.cells = N->N->_
all b' : Board - first, b : b'.prev {
not finished[b,b'] => play[b,b']
}
}
```
Run
With the run we can look for certain types of solutions. In this example we try to find a game where O
wins with a righ diagonal ...
```alloy
run { some b : Board | rightdiag in b.cells.(O) } for 11 but 3 int
```
This provides the following output in Alloy 5 Table view (table is reorded from beta 5):
┌──────────┬──────┬────┬───┬───┐
│this/Board│cells │move│to │won│
├──────────┼─┬─┬──┼────┼───┼───┤
│Board⁰ │0│0│_⁰│_⁰ │ │_⁰ │
│ │ ├─┼──┼────┤ ├───┤
│ │ │1│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │1│0│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │2│0│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
├──────────┼─┼─┼──┼────┼─┬─┼───┤
│Board¹ │0│0│_⁰│X⁰ │2│1│_⁰ │
│ │ ├─┼──┼────┼─┴─┼───┤
│ │ │1│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │1│0│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │2│0│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
├──────────┼─┼─┼──┼────┼─┬─┼───┤
│Board² │0│0│_⁰│O⁰ │1│2│_⁰ │
│ │ ├─┼──┼────┼─┴─┼───┤
│ │ │1│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │1│0│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│O⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │2│0│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
├──────────┼─┼─┼──┼────┼─┬─┼───┤
│Board³ │0│0│_⁰│X⁰ │0│1│_⁰ │
│ │ ├─┼──┼────┼─┴─┼───┤
│ │ │1│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │1│0│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│O⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │2│0│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
├──────────┼─┼─┼──┼────┼─┬─┼───┤
│Board⁴ │0│0│O⁰│O⁰ │0│0│_⁰ │
│ │ ├─┼──┼────┼─┴─┼───┤
│ │ │1│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │1│0│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│O⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │2│0│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
├──────────┼─┼─┼──┼────┼─┬─┼───┤
│Board⁵ │0│0│O⁰│X⁰ │2│0│_⁰ │
│ │ ├─┼──┼────┼─┴─┼───┤
│ │ │1│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │1│0│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│O⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │2│0│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
├──────────┼─┼─┼──┼────┼─┬─┼───┤
│Board⁶ │0│0│O⁰│O⁰ │1│1│_⁰ │
│ │ ├─┼──┼────┼─┴─┼───┤
│ │ │1│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │1│0│_⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│O⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│O⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │2│0│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
├──────────┼─┼─┼──┼────┼─┬─┼───┤
│Board⁷ │0│0│O⁰│X⁰ │1│0│_⁰ │
│ │ ├─┼──┼────┼─┴─┼───┤
│ │ │1│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │1│0│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│O⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│O⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │2│0│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
├──────────┼─┼─┼──┼────┼─┬─┼───┤
│Board⁸ │0│0│O⁰│O⁰ │2│2│_⁰ │
│ │ ├─┼──┼────┼─┴─┼───┤
│ │ │1│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │1│0│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│O⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│O⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │2│0│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│O⁰│ │ │ │
├──────────┼─┼─┼──┼────┼───┼───┤
│Board⁹ │0│0│O⁰│_⁰ │ │O⁰ │
│ │ ├─┼──┼────┤ ├───┤
│ │ │1│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │1│0│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│O⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│O⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │2│0│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│O⁰│ │ │ │
├──────────┼─┼─┼──┼────┼───┼───┤
│Board¹⁰ │0│0│O⁰│_⁰ │ │O⁰ │
│ │ ├─┼──┼────┤ ├───┤
│ │ │1│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│_⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │1│0│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│O⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│O⁰│ │ │ │
│ ├─┼─┼──┤ │ │ │
│ │2│0│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │1│X⁰│ │ │ │
│ │ ├─┼──┤ │ │ │
│ │ │2│O⁰│ │ │ │
└──────────┴─┴─┴──┴────┴───┴───┘
I had some fun implementing this example in Lightning. You might find my results interesting:
Lightning (lightning-workbench) is a language workbench based on Alloy allowing you to define a concrete syntax for any of your Alloy specifications. The concrete syntax is defined through the use of a dedicated model transformation language called f-alloy ( an Alloy variant having the property of being interpretable rather than analyzable ), so you might need a bit of time getting used to it.
The tool's available as an eclipse plugin (update site).
Here's an archive file containing the source of the TicTacToe project illustrated in the above picture, so that you can play around the example by yourself.

Resources