+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:xlink="http://www.w3.org/1999/xlink"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="256"
- height="256"
- id="svg4386"
- version="1.1"
- inkscape:version="0.48.5 r10040"
- sodipodi:docname="icon.svg">
- <defs
- id="defs4388">
- <radialGradient
- r="7.7875042"
- fy="141.32838"
- fx="106.50102"
- cy="141.3284"
- cx="106.50099"
- gradientTransform="scale(1.02976,0.9711)"
- gradientUnits="userSpaceOnUse"
- id="radialGradient8339"
- xlink:href="#linearGradient5606"
- inkscape:collect="always" />
- <linearGradient
- id="linearGradient5606">
- <stop
- id="stop5608"
- offset="0.0000000"
- style="stop-color:#000000;stop-opacity:1.0000000;" />
- <stop
- id="stop5614"
- offset="0.20000000"
- style="stop-color:#000000;stop-opacity:1.0000000;" />
- <stop
- id="stop5616"
- offset="0.30000001"
- style="stop-color:#ffffff;stop-opacity:1.0000000;" />
- <stop
- id="stop5610"
- offset="1.0000000"
- style="stop-color:#ffffff;stop-opacity:1.0000000;" />
- </linearGradient>
- <filter
- inkscape:label="Desaturate"
- x="0"
- y="0"
- width="1"
- height="1"
- inkscape:menu="Color"
- inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero"
- color-interpolation-filters="sRGB"
- id="filter3078">
- <feColorMatrix
- type="saturate"
- values="0"
- id="feColorMatrix3080" />
- </filter>
- <filter
- inkscape:label="Desaturate"
- x="0"
- y="0"
- width="1"
- height="1"
- inkscape:menu="Color"
- inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero"
- color-interpolation-filters="sRGB"
- id="filter3082">
- <feColorMatrix
- type="saturate"
- values="0"
- id="feColorMatrix3084" />
- </filter>
- <filter
- inkscape:label="Desaturate"
- x="0"
- y="0"
- width="1"
- height="1"
- inkscape:menu="Color"
- inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero"
- color-interpolation-filters="sRGB"
- id="filter3086">
- <feColorMatrix
- type="saturate"
- values="0"
- id="feColorMatrix3088" />
- </filter>
- <filter
- inkscape:label="Desaturate"
- x="0"
- y="0"
- width="1"
- height="1"
- inkscape:menu="Color"
- inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero"
- color-interpolation-filters="sRGB"
- id="filter3090">
- <feColorMatrix
- type="saturate"
- values="0.71980676328502413"
- id="feColorMatrix3092" />
- </filter>
- </defs>
- <sodipodi:namedview
- id="base"
- pagecolor="#ffffff"
- bordercolor="#666666"
- borderopacity="1.0"
- inkscape:pageopacity="0.0"
- inkscape:pageshadow="2"
- inkscape:zoom="0.98994949"
- inkscape:cx="76.110583"
- inkscape:cy="101.8389"
- inkscape:document-units="px"
- inkscape:current-layer="layer1"
- showgrid="false"
- inkscape:window-width="1279"
- inkscape:window-height="1377"
- inkscape:window-x="1279"
- inkscape:window-y="29"
- inkscape:window-maximized="0" />
- <metadata
- id="metadata4391">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title />
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <g
- inkscape:label="Layer 1"
- inkscape:groupmode="layer"
- id="layer1"
- transform="translate(0,-796.36218)">
- <g
- inkscape:export-ydpi="72.000000"
- inkscape:export-xdpi="72.000000"
- inkscape:export-filename="/home/sam/monsterz/monsterz/tiles.png"
- transform="matrix(4.2708842,0,0,4.1991018,0.37397342,-2109.743)"
- id="g8300"
- style="filter:url(#filter3090)">
- <g
- style="stroke:none"
- inkscape:export-ydpi="72.000000"
- inkscape:export-xdpi="72.000000"
- inkscape:export-filename="/home/sam/monsterz/monsterz/test.png"
- transform="matrix(0.823543,0,0,0.832736,-337.8784,464.3823)"
- id="g8288">
- <path
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc"
- id="path8290"
- d="M 427.22149,306.26985 C 400,308.61218 417.5,276.66759 427.5,286.11218"
- style="fill:#01ff03;fill-opacity:1;fill-rule:evenodd" />
- <path
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc"
- id="path8292"
- d="m 467.5,283.61218 c 18.75,-6.25 17.5,28.75 -2.5,18.75"
- style="fill:#01ff03;fill-opacity:1;fill-rule:evenodd" />
- <path
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="ccc"
- id="path8294"
- d="M 428.58387,304.20437 C 406.97719,274.86218 485,259.86218 465,302.36218 c 53.75,37.5 -81.25,70 -36.41613,1.84219 z"
- style="fill:#01ff03;fill-opacity:1;fill-rule:evenodd" />
- <path
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc"
- id="path8296"
- d="m 420,292.36218 c -1.95031,4.4858 0,6.25 1.25,7.5"
- style="fill:none" />
- <path
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc"
- id="path8298"
- d="m 473.75,289.34798 c 0.60933,3.0142 1.25,4.2642 -1.25,7.44624"
- style="fill:none" />
- </g>
- <path
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="ccc"
- id="path8275"
- d="m 20,747.36218 c 23.207741,2.5 36.25,-13.75 31.25,-20 -1.25,10 -18.03209,20.56008 -31.25,20 z"
- style="fill:#000000;fill-opacity:0.1372549;fill-rule:evenodd;stroke:none" />
- <g
- id="g8283">
- <path
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="ccc"
- id="path8277"
- d="M 14.300999,703.00709 C 5.5600809,697.36218 -4.2738645,716.11218 7.5,718.61218 c -5,-5.67209 -2.5,-13.75 6.800999,-15.60509 z"
- style="fill:#ffffff;fill-opacity:0.41176471;fill-rule:evenodd;stroke:none" />
- <path
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="ccc"
- id="path8279"
- d="m 45,698.61218 c -13.75,-11.25 -42.5,-0.73064 -28.75,20 -3.75,-12.5 11.25,-25 28.75,-20 z"
- style="fill:#ffffff;fill-opacity:0.41176471;fill-rule:evenodd;stroke:none" />
- <path
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="ccc"
- id="path8281"
- d="m 57.5,708.61218 c 1.25,-9.4761 -12.5,-11.25 -10,-4.41905 5.560081,-1.83095 7.321465,0.72043 10,4.41905 z"
- style="fill:#ffffff;fill-opacity:0.41176471;fill-rule:evenodd;stroke:none" />
- </g>
- <g
- style="fill:none"
- inkscape:export-ydpi="72.000000"
- inkscape:export-xdpi="72.000000"
- inkscape:export-filename="/home/sam/monsterz/monsterz/test.png"
- transform="matrix(0.823543,0,0,0.832736,-337.8784,464.3823)"
- id="g7882">
- <path
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc"
- id="path7659"
- d="M 427.22149,306.26985 C 400,308.61218 417.5,276.66759 427.5,286.11218"
- style="stroke:#000000;stroke-width:3.11108565;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" />
- <path
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc"
- id="path7661"
- d="m 467.5,283.61218 c 18.75,-6.25 17.5,28.75 -2.5,18.75"
- style="stroke:#000000;stroke-width:3.11108565;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" />
- <path
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="ccc"
- id="path7657"
- d="M 428.58387,304.20437 C 406.97719,274.86218 485,259.86218 465,302.36218 c 53.75,37.5 -81.25,70 -36.41613,1.84219 z"
- style="stroke:#000000;stroke-width:3.11108565;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" />
- <path
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc"
- id="path7663"
- d="m 420,292.36218 c -1.95031,4.4858 0,6.25 1.25,7.5"
- style="stroke:#000000;stroke-width:3.11108565;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" />
- <path
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc"
- id="path7665"
- d="m 473.75,289.34798 c 0.60933,3.0142 1.25,4.2642 -1.25,7.44624"
- style="stroke:#000000;stroke-width:3.11108565;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" />
- </g>
- </g>
- <g
- inkscape:export-ydpi="72.000000"
- inkscape:export-xdpi="72.000000"
- inkscape:export-filename="/home/sam/monsterz/monsterz/tiles.png"
- transform="matrix(4.3642738,0,0,-0.17844664,-531.2477,940.61654)"
- id="g8323"
- style="filter:url(#filter3090)">
- <path
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc"
- id="path8325"
- d="M 150.47232,132.96909 150,134.86218"
- style="fill:none;stroke:#000000;stroke-width:12.36332512;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" />
- <path
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc"
- id="path8327"
- d="m 153.75,132.96909 0.47232,1.89309"
- style="fill:none;stroke:#000000;stroke-width:12.36332512;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" />
- </g>
- <g
- inkscape:export-ydpi="72.000000"
- inkscape:export-xdpi="72.000000"
- inkscape:export-filename="/home/sam/monsterz/monsterz/tiles.png"
- transform="matrix(4.3642695,0,0,4.3642084,-733.54667,664.62487)"
- id="g8329"
- style="filter:url(#filter3090)">
- <path
- inkscape:connector-curvature="0"
- inkscape:export-ydpi="72.000000"
- inkscape:export-xdpi="72.000000"
- inkscape:export-filename="/home/sam/monsterz/monsterz/test.png"
- sodipodi:nodetypes="cc"
- id="path8331"
- d="m 191.8115,62.362183 c 2.83835,1.82034 7.56893,1.99609 11.3534,0"
- style="fill:none;stroke:#ffffff;stroke-width:2.49998164;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.41176471" />
- <path
- inkscape:connector-curvature="0"
- inkscape:export-ydpi="72.000000"
- inkscape:export-xdpi="72.000000"
- inkscape:export-filename="/home/sam/monsterz/monsterz/test.png"
- sodipodi:nodetypes="cc"
- id="path8333"
- d="m 193.0615,64.152194 c 2.83835,1.82034 7.56893,1.99609 11.3534,0"
- style="fill:none;stroke:#000000;stroke-width:2.49998164;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.1372549" />
- <path
- inkscape:connector-curvature="0"
- inkscape:export-ydpi="72.000000"
- inkscape:export-xdpi="72.000000"
- inkscape:export-filename="/home/sam/monsterz/monsterz/test.png"
- sodipodi:nodetypes="cc"
- id="path8335"
- d="m 192.5,63.430263 c 2.83835,1.82034 7.56893,1.99609 11.3534,0"
- style="fill:none;stroke:#000000;stroke-width:2.49998164;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" />
- </g>
- <path
- inkscape:export-ydpi="72.000000"
- inkscape:export-xdpi="72.000000"
- inkscape:export-filename="/home/sam/monsterz/monsterz/tiles.png"
- transform="matrix(4.2024883,1.3865396,-2.0345896,3.8609453,-52.336063,179.80496)"
- d="m 115.86819,137.36218 a 6.1977272,6.1227274 0 1 1 -12.39545,0 6.1977272,6.1227274 0 1 1 12.39545,0 z"
- sodipodi:ry="6.1227274"
- sodipodi:rx="6.1977272"
- sodipodi:cy="137.36218"
- sodipodi:cx="109.67046"
- id="path8337"
- style="fill:url(#radialGradient8339);fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:2.49998115999999992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;filter:url(#filter3090)"
- sodipodi:type="arc" />
- </g>
-</svg>
+++ /dev/null
-# Monsterz - Chains of Friends
-#
-# 2010-2014 (c) Jean Privat <jean@pryen.org>
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the Do What The Fuck You Want To
-# Public License, Version 2, as published by Sam Hocevar. See
-# http://sam.zoy.org/projects/COPYING.WTFPL for more details.
-
-# Full UI for the game
-module friendz is
- app_name("ChainZ of FriendZ")
- app_version(0, 1, git_revision)
-end
-
-import app::audio
-import mnit
-import realtime
-import solver
-import mnit::tileset
-import app::data_store
-
-intrude import grid
-intrude import level
-
-redef class Grid
- # Zoom level in %
- # higher means more dense grid
- var ratio = 100
-
- # Various grid sizes from large to small (16x16, 12x12, 8x8, 6x6)
- var ratios: Array[Int] = [200, 150, 100, 75]
-
- redef fun resize(w,h)
- do
- super
- for r in ratios do
- if w*100/r <= 8 and h*100/r <= 8 then self.ratio = r
- end
- end
-end
-
-# * ENTITIES ****************************************************************
-
-# A game entity is something that is displayed and may interact with the player.
-abstract class Entity
- # The associated game
- var game: Game
-
- # X left coordinate (in pixel).
- var x: Int
-
- # Y top coordinate (in pixel).
- var y: Int
-
- # X right coordinate (in pixel).
- fun x2: Int do return x + width
-
- # Y bottom coordinate (in pixel).
- fun y2: Int do return y + height
-
- # Width
- var width: Int
-
- # Height
- var height: Int
-
- # Tool tip text (if any)
- var over: nullable String = null
-
- # can the entity intercepts drag ang drop events?
- var draggable = false
-
- # Draw function. To implement
- fun draw(ctx: Display) do end
-
- # Update function. Called each loop. To implement
- fun update do end
-
- # Enter function. Called when the cursor enter in the element. To implement
- fun enter(ev: Event) do end
-
- # Click function. Called when the player click in the element.
- # (or activate it with a shortcut).
- fun click(ev: Event) do end
-
- # keyboard shortcut do activate the entity, if any
- var shortcut: nullable String = null
-
- # Are events received?
- var enabled = true
-
- fun bw: Int do return game.bw
- fun bh: Int do return game.bh
-
- # Should the entity be redrawn
- var dirty = true
-end
-
-# TEXT BUTTONS ***********************************************************/
-
-# Button entity displayed as a simple text.
-# if `over1` is null, then the button is a simple pasive label
-# if `over1` is set but `over2` is null, then the button is a normal button
-# if both `over1` and `over2` arew set, then the button is a toggleable button with two states
-class TextButton
- super Entity
- var str: String
- init(game: Game, str: String, x,y: Int, color: nullable String, over, over2: nullable String)
- do
- var w = 10 # TODO
- super(game, x,y,w,24)
- self.str = str
- self.color = color or else "purple"
- self.over = over
- self.over1 = over
- self.over2 = over2
- self.textx = x
- if self.toggleable then
- self.x -= bw/2 + 4
- end
- end
-
- var color: String
-
- # The description of the button action
- var over1: nullable String
- # The description of the state2 button action
- var over2: nullable String
-
- # is the button a two-state button
- fun toggleable: Bool do return over2 != null
-
- # is the toggleable button in its second state?
- var toggled = false
-
- # ttl for highlighting
- var ttl = 0
-
- # position of the start of the text
- # in a toggleable button, there is space for the mark between `x` and `textx`
- var textx: Int
-
- redef fun draw(ctx) do
- if self.toggleable then
- var w
- if self.toggled or not self.enabled then w = 6 else w = 7
- ctx.blit(game.img2[w,0], self.x, self.y)
- end
- var c
- if self.enabled then c = self.color else c = "gray"
- var c2= null
- if self.ttl > 0 then c2 = "rgba(255,255,255,{self.ttl/10})"
- ctx.textx(self.str, self.textx, self.y, self.height, c, c2)
- self.width = ctx.measureText(self.str, self.height)
- if self.toggleable then self.width += bw/2 + 4
- end
-
- redef fun update
- do
- if game.statusbar.over_what != self and self.ttl > 0 then
- self.ttl-=1
- self.dirty = true
- end
- end
-
- redef fun enter(ev)
- do
- if over1 == null then return
- if not self.enabled then return
- game.snd_click.play
- self.ttl = 10
- self.dirty = true
- self.enter2
- end
-
- # Called by `enter` do perform additionnal work if the button is active
- # Specific button should implement this instead of `enter`
- fun enter2 do end
-
- redef fun click(ev)
- do
- if not self.enabled then
- game.snd_bing.play
- else
- if self.toggleable then
- self.toggled = not self.toggled
- if self.toggled then self.over = self.over2 else self.over = self.over1
- game.statusbar.over_txt = self.over
- end
- game.snd_whip.play
- end
- self.click2(ev)
- end
-
- # Called by `click` do perform additionnal work if the button is active
- # Specific button should implement this instead of `click`
- fun click2(ev: Event) do end
-
-end
-
-# LEVEL BUTTONS ***********************************************************/
-
-# button to play a level in the menu screen
-class LevelButton
- super Entity
-
- # The associated level to play
- var level: Level
-
- init(l: Level)
- do
- self.level = l
- var i = l.number
- super(l.game, (i%5)*56 + 54, (i/5)*56 + 55, l.game.bw, l.game.bh)
-
- self.over = self.level.fullname
- if self.level.get_state >= l.l_won then
- if game.levels[9].get_state >= l.l_won then self.over += " --- {self.level.score}/{self.level.gold}"
- else if self.level.get_state >= l.l_open then
- if game.levels[9].get_state >= l.l_open then self.over += " --- ?/{self.level.gold}"
- end
- self.enabled = l.get_state >= l.l_open or game.cheated
- end
-
- redef fun draw(ctx)
- do
- var l = level
- var s = self.level.get_state
- var ix = 5 + l.number % 2
- var iy = 0
- if s == l.l_disabled then
- ix = 3
- iy = 3
- else if s == l.l_open then
- ix = 1
- iy = 1
- ctx.blit(game.img[ix,iy], self.x, self.y)
- ix = 0
- iy = 0
- end
- ctx.blit(game.img[ix,iy], self.x, self.y)
-
- if s == l.l_gold then
- ctx.blit(game.img2[7,0], self.x + bw*5/8, self.y-bh*1/8)
- end
- ctx.textx(self.level.name, self.x+5, self.y+5, 24, null, null)
- end
-
- redef fun click(ev)
- do
- if self.enabled then
- game.snd_whip.play
- game.play(self.level)
- else
- game.snd_bing.play
- game.statusbar.set_tmp("Locked level", "red")
- end
- end
-
-end
-
-# ACHIEVEMENTS ************************************************************/
-
-# Achievement (monster-like) button in the menu screen
-class Achievement
- super Button
-
- # The number of the achievement (0 is first)
- var number: Int
-
- # The name of the achievement
- var name: String
-
- init(game: Game, i: Int, name: String)
- do
- super(game, 5*56 + 54, i*56 + 55, game.bw, game.bh)
- self.over = name
- self.number = i
- self.name = name
- var l = game.levels[number*5+4]
- enabled = l.get_state >= l.l_won
- if self.enabled then self.over = name + " (unlocked)" else self.over = name + " (locked)"
- end
-
- redef fun draw(ctx)
- do
- var w
- if self.enabled then w = 5 else w = 3
- ctx.blit(game.img[w,self.number+5], self.x, self.y)
- end
-
- redef fun click(ev)
- do
- if not self.enabled then
- game.snd_bing.play
- game.statusbar.set_tmp("Locked achievement!", "red")
- else
- game.snd_whip.play
- self.click2(ev)
- end
- end
-
- fun click2(ev: Event) do
- # TODO
- end
-end
-
-
-# BOARD (THE GRID) *******************************************************/
-
-# The board game element.
-class Board
- super Entity
- init(game: Game)
- do
- super(game, game.xpad, game.ypad, 8*game.bw, 8*game.bh)
- draggable = true
- end
-
- redef fun draw(ctx)
- do
- var grid = game.grid
- var bwr = bw*100/grid.ratio
- var bhr = bh*100/grid.ratio
- var w = grid.width
- var h = grid.height
- if game.selected_button == game.button_size then
- bwr = bw/2
- bhr = bh/2
- w = game.gw
- h = game.gh
- end
- self.x = game.xpad+(48*8/2)-w*bwr/2
- self.y = game.ypad+(48*8/2)-h*bhr/2
- self.width = w*bwr
- self.height = h*bhr
- for i in [0..w[ do
- for j in [0..h[ do
- var t = grid.grid[i][j]
- var dx = i * bwr + self.x
- var dy = j * bhr + self.y
- if (i+j)%2 == 0 then
- ctx.blit_scaled(game.img[5,0], dx, dy, bwr, bhr)
- else
- ctx.blit_scaled(game.img[6,0], dx, dy, bwr, bhr)
- end
- if t.fixed then
- if t.shape != null and not game.editing then
- #ctx.drawImage(game.img, t.shape.x*bw, (2+t.shape.y)*bh, bw, bh, i * bwr + self.x, j * bhr + self.y, bwr, bhr)
- ctx.blit_scaled(game.img[3,3], dx, dy, bwr, bhr)
- else
- ctx.blit_scaled(game.img[3,3], dx, dy, bwr, bhr)
- end
- end
- if t.kind>0 then
- var m = grid.monsters[t.kind]
- var s = 0
- if t.blink > 0 then s = 1
- if t.nexts > 2 then s = 3
- if t.nexts == 0 then s = 6
- if m.chain then s = 5
- if t.shocked>0 then s = 2
- ctx.blit_scaled(game.img[s,(4+t.kind)], dx, dy, bwr, bhr)
- end
- #ctx.textx(t.chain_mark.to_s, dx, dy, 20, "", null)
- end
- end
- if game.selected_button == game.button_size then
- var x0 = self.x
- var x1 = (grid.width) * bwr - bwr/2 + self.x
- var y0 = self.y
- var y1 = (grid.height) * bhr - bhr/2 + self.y
- ctx.blit_scaled(game.img2[0,0], x0, y0, bwr/2, bhr/2)
- ctx.blit_scaled(game.img2[1,0], x1, y0, bwr/2, bhr/2)
- ctx.blit_scaled(game.img2[1,1], x1, y1, bwr/2, bhr/2)
- ctx.blit_scaled(game.img2[0,1], x0, y1, bwr/2, bhr/2)
- ctx.textx("{grid.width}x{grid.height}",self.x + grid.width*bwr/2,self.y+grid.height*bhr/2,20,"orange",null)
- end
- end
-
- redef fun update
- do
- var grid = game.grid
- for i in [0..grid.width[ do
- for j in [0..grid.height[ do
- var t = grid.grid[i][j]
- if t.kind == 0 then continue
- if t.blink > 0 then
- t.blink-=1
- self.dirty=true
- end
- if t.shocked > 0 then
- t.shocked-=1
- self.dirty=true
- else if 100.rand == 0 then
- t.blink = 5
- self.dirty=true
- end
- end
- end
- end
-
- # Last clicked tile
- # Uded to filter drag events
- private var last: nullable Tile = null
-
- redef fun click(ev)
- do
- var grid = game.grid
- var r = grid.ratio
- if game.selected_button == game.button_size then r = 200
- var x = ev.game_x * r / bw / 100
- var y = ev.game_y * r / bh / 100
- var t = grid.grid[x][y]
-
- if ev.drag and last == t then return
- last = t
-
- if game.selected_button != game.button_size and (x>=grid.width or y>=grid.height) then return
-
- # print "{ev.game_x},{ev.game_y},{ev.drag} -> {x},{y}:{t}"
-
- if game.selected_button != null then
- game.selected_button.click_board(ev, t)
- end
- end
-end
-
-# BUTTONS *****************************************************************/
-
-# A in-game selectable button for monsters or tools
-class Button
- super Entity
-
- # The x tile
- var imgx: Int = 0
-
- # The y tile
- var imgy: Int = 0
-
- # The associated monster tile
- # >0 for monsters, <=0 for tools
- var kind = 0
-
- redef fun draw(ctx)
- do
- ctx.blit(game.img[self.imgx, self.imgy], self.x, self.y)
- if game.selected_button == self then ctx.blit(game.img[0, 0], self.x, self.y)
- end
-
- redef fun click(ev)
- do
- var sel = game.selected_button
- if game.selected_button == game.button_size then game.board.dirty=true
- if sel != null then sel.dirty=true
- game.selected_button = self
- game.snd_click.play
- end
-
- # Current inputed chain
- # Used for drag
- private var chain = new Array[Tile]
-
- # Board click. Called when the player click on the board with the button selected.
- fun click_board(ev: Event, t: Tile)
- do
- game.score.dirty = true
- if ev.drag and self.kind>0 and not chain.is_empty then
- if self.chain.length >= 2 and self.chain[1] == t then
- var t2 = self.chain.shift
- game.snd_click.play
- if t2.fixed and not game.editing then return
- t2.update(0)
- return
- end
- if t.fixed and t.kind == self.kind then
- self.chain.unshift(t)
- game.snd_click.play
- return
- end
- if (self.chain[0].x - t.x).abs + (self.chain[0].y - t.y).abs != 1 then return
- if t.fixed and not game.editing then
- game.snd_bing.play
- return
- end
- if t.kind != 0 and t.kind != self.kind then
- t.shocked = 5
- game.snd_duh.play
- return
- end
- self.chain.unshift(t)
- if t.kind == self.kind then return
- game.snd_click.play
- t.update(self.kind)
- return
- end
-
- if t.fixed and not game.editing then
- if t.kind == 0 then
- game.snd_bing.play
- return
- end
- if t.kind != self.kind and not ev.drag then
- game.buttons[t.kind].click(ev)
- game.buttons[t.kind].chain = [t]
- else
- self.chain = [t]
- game.snd_bing.play
- end
- return
- end
- if t.fixed and game.editing and self == game.button_erase and t.kind == 0 then
- t.fixed = false
- game.snd_click.play
- return
- end
-
- var nkind = 0 # the new kind
- if ev.drag then
- # Here we clean
- if t.kind == 0 then return
- if self.kind != 0 and t.kind != self.kind then
- t.shocked = 5
- game.snd_duh.play
- return
- end
- nkind = 0
- else if t.kind != self.kind then
- nkind = self.kind
- self.chain = [t]
- else if t.kind != 0 then
- nkind = 0
- self.chain.clear
- end
- if nkind == t.kind then return
- game.snd_click.play
- t.update(nkind)
- end
-end
-
-# A monster button
-class MonsterButton
- super Button
-
- # TTL for the monster being angry
- var angries = 0
- # TTL for the monster being happy
- var happy = 0
- # TTL for the monster being shocked
- var shocked = 0
- # TTL for the monster blinking
- var blink = 0
-
- init(game: Game, i: Int)
- do
- self.game = game
- var x = 440 + 58 * ((i-1).abs%3)
- var y = 150 + (bh+5) * ((i-1)/3)
- super(game, x, y, game.bw, game.bh)
- if i == 0 then return
- kind = i
- imgx = 0
- imgy = (4+i)
- over = game.colors[i] + " monster ({i})"
- shortcut = i.to_s # code for 1 trough 9
- end
-
- redef fun click(ev)
- do
- super
- self.shocked = 5
- end
-
- redef fun update
- do
- if self.happy > 0 then
- self.happy-=1
- self.dirty=true
- end
- if self.shocked > 0 then
- self.shocked-=1
- self.dirty=true
- end
- if self.blink > 0 then
- self.blink-=1
- self.dirty=true
- else if 100.rand == 0 then
- self.blink = 5
- self.dirty=true
- end
- end
-
- redef fun draw(ctx)
- do
- var s = self.imgx
- if self.angries>0 then
- s += 3
- else if self.happy > 5 then
- s += 5
- else if self.shocked > 0 then
- s += 5
- else if self.blink > 0 then
- s += 1
- end
- ctx.blit(game.img[s, self.imgy], self.x, self.y)
- if game.selected_button == self then ctx.blit(game.img[0, 0], self.x, self.y)
- end
-end
-
-# Erase button.
-class EraseButton
- super Button
- init(game: Game) do
- super(game, 440, 92, game.bh, 22+game.bh)
- imgx = 4
- imgy = 13
- kind = 0
- over = "Eraser (0)"
- shortcut = "0"
- end
-end
-
-# Metal (fixed) button.
-class MetalButton
- super Button
- init(game: Game)
- do
- super(game, 498, 92, game.bh, 20+game.bh)
- imgx = 3
- imgy = 3
- kind = -1
- over = "Metal block (q)"
- shortcut = "q"
- end
-
- private var fixed = false
-
- redef fun click_board(ev,t)
- do
- if not ev.drag then self.fixed = not t.fixed
- if t.fixed == self.fixed then return
- t.fixed = self.fixed
- game.snd_click.play
- end
-end
-
-# Resize button.
-class ResizeButton
- super Button
-
- init(game: Game)
- do
- super(game,556, 92, game.bh, 20+game.bh)
- kind = -2
- over = "Resize the grid"
- end
-
- redef fun draw(ctx)
- do
- for i in [0..3[ do
- for j in [0..3[ do
- var x = self.x + i*bw/3
- var y = self.y + j*bh/3
- ctx.blit_scaled(game.img[5+(i+j)%2,0], x, y, bw/3, bh/3)
- end
- end
- if game.selected_button == self then ctx.blit(game.img[0, 0], self.x, self.y)
- end
-
- redef fun click(ev)
- do
- if game.selected_button != game.button_size then
- super
- else
- game.selected_button = null
- game.board.dirty=true
- end
- end
-
- redef fun click_board(evt, t)
- do
- var grid = t.grid
- var w = t.x+1
- var h = t.y+1
- if w < 3 or h < 3 then
- game.snd_bing.play
- game.statusbar.set_tmp("Too small!", "red")
- return
- end
- var aborts = false
- for i in [0..grid.width[ do
- for j in [0..grid.height[ do
- if i>=w or j>=h then
- var t2 = grid.grid[i][j]
- if t2.kind > 0 then
- aborts = true
- t2.shocked = 5
- end
- end
- end
- end
- if aborts then
- game.snd_duh.play
- game.statusbar.set_tmp("Monsters on the way!", "red")
- return
- end
- game.snd_click.play
- grid.resize(w,h)
- end
-end
-
-# Inactive area used to display the score
-class Score
- super Entity
- init(game: Game)
- do
- super(game,440,310,199,62)
- end
- redef fun draw(ctx)
- do
- ctx.textx("MONSTERS: {game.grid.number}",self.x,self.y+1,21,"cyan",null)
- var level = game.level
- if level == null then return
- if level.get_state >= level.l_won then
- ctx.textx("BEST: {level.score}",self.x,self.y+22,21,"pink", null)
- else
- ctx.textx("BEST: -",self.x,self.y+22,21,"pink", null)
- end
- if game.levels[9].get_state >= level.l_won then
- if level.is_challenge then
- ctx.textx("GOAL: {level.gold}",self.x,self.y+44,21,"yellow",null)
- else
- ctx.textx("GOLD: {level.gold}",self.x,self.y+44,21,"yellow",null)
- end
- end
- end
-end
-
-# Status bar element.
-class StatusBar
- super Entity
- init(game: Game)
- do
- super(game,24, 440, 418-24, 30)
- end
-
- # Permanant text, if any
- var main_txt: nullable String = null
-
- # Text to display when the cursor if over an entity (`over_what`), if any
- var over_txt: nullable String = null
-
- # What is the entity for `over_txt`
- var over_what: nullable Entity
-
- # Text to temporally display, for some game event, if any
- var tmp_txt: nullable String = null
-
- # time-to-live for the `tmp_txt`
- var tmp_txt_ttl = 0
-
- # Color used to display `tmp_txt`
- var tmp_txt_color: nullable String = null
-
- # reset the status
- fun clear do
- self.main_txt = null
- self.over_txt = null
- self.tmp_txt = null
- self.over = null
- end
-
- # set a temporary text
- fun set_tmp(txt, color: String)
- do
- print "***STATUS** {txt}"
- self.tmp_txt = txt
- self.tmp_txt_ttl = 60
- self.tmp_txt_color = color
- end
-
- redef fun draw(ctx)
- do
- var tmp_txt = self.tmp_txt
- var over_txt = self.over_txt
- var main_txt = self.main_txt
- if tmp_txt != null and self.tmp_txt_ttl>0 then
- ctx.textx(tmp_txt,24,442,24,self.tmp_txt_color,null)
- else if over_txt != null then
- ctx.textx(over_txt,24,442,24,"yellow",null)
- else if main_txt != null then
- ctx.textx(main_txt,24,442,24,"white",null)
- end
- end
-
- redef fun update
- do
- if self.tmp_txt_ttl>0 then
- self.tmp_txt_ttl-=1
- self.dirty=true
- end
- end
-end
-
-# ************************************************************************/
-
-redef class Display
- # Display a text
- fun textx(str: String, x, y, height: Int, color, color2: nullable String)
- do
- #var w = measureText(str, height)
- #rect(x,y,w,height)
- text(str.to_upper, app.game.font, x, y)
- end
-
- # give the width for a giver text
- fun measureText(str: String, height: Int): Int
- do
- var font = app.game.font
- return str.length * (font.width + font.hspace.to_i)
- end
-
- # displays a debug rectangle
- fun rect(x,y,w,h:Int)
- do
- var image = once app.load_image("hitbox.png")
- blit_scaled(image, x, y, w, h)
- end
-end
-
-# Simple basic class for event
-class Event
- # Is a drag event?
- var drag = false
- # screen x
- var offset_x: Int
- # screen y
- var offset_y: Int
- # entity x
- var game_x = 0
- # entity y
- var game_y = 0
- # key pressed
- var char_code: String
-end
-
-redef class Game
- # width of a tile, used for most width reference in the game
- var bw = 48
- # height a tile, used for most width reference in the game
- var bh = 48
- # x-coordinate of the board (padding)
- var xpad = 24
- # y-coordinate of the board (padding)
- var ypad = 24
-
- # Load tiles
-
- # Basic tileset
- var img = new TileSet(app.load_image("tiles2.png"),48,48)
-
- # Sub tileset (for marks or other)
- var img2 = new TileSet(app.load_image("tiles2.png"),24,24)
-
- # background image
- var back: Image = app.load_image("background.png")
-
- # Logo image
- var logo: Image = app.load_image("logo.png")
-
- # Font
- var font = new TileSetFont(app.load_image("deltaforce_font.png"), 16, 17, "ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789.:;!?\"'() -,/")
-
- # DISPLAY *****************************************************************
-
- # Is the game in editing mode
- var editing = false
-
- # The selected button, if any
- var selected_button: nullable Button = null
-
- # SOUND
-
- # Is the music muted?
- var music_muted: Bool = app.data_store["music_muted"] == true
-
- # Is the sound effects muted?
- var sfx_muted: Bool = app.data_store["sfx_muted"] == true
-
- # The background music resource. */
- var music = new Music("music.ogg")
-
- # Click sound
- var snd_click = new Sound("click.wav")
-
- # Wining soulf
- var snd_win = new Sound("level.wav")
-
- # Shocked sound
- var snd_duh = new Sound("duh.wav")
-
- # metal sound
- var snd_bing = new Sound("bing.wav")
-
- # transition sound
- var snd_whip = new Sound("whip.wav")
-
-
- # INPUT ******************************************************************
-
- # Current grid edited (if any).
- var grid_edit: nullable Grid = null
-
- # Sequence of current entities
- var entities = new Array[Entity]
-
- # The current statusbar
- var statusbar = new StatusBar(self)
-
- # The grid board
- var board = new Board(self)
-
- # The current score board
- var score = new Score(self)
-
- # Monster button game elements.
- var buttons = new Array[MonsterButton]
-
- # MetalButton
- var button_wall = new MetalButton(self)
-
- # EraseButton
- var button_erase = new EraseButton(self)
-
- # ResizeButton
- var button_size = new ResizeButton(self)
-
- # Cheat mode enabled?
- var cheated = false
-
- init
- do
- init_buttons
- entities.clear
- title
-
- if not music_muted then music.play
- end
-
- # fill `buttons`
- fun init_buttons
- do
- for i in [0..9] do
- buttons[i] = new MonsterButton(self, i)
- end
- end
-
- # Play a level in player mode.
- fun play(l: Level)
- do
- save # save the previous level grid
- level = l
- grid.load(level.saved_str or else level.str)
- init_play_menu(false)
- if level.status != "" then
- statusbar.main_txt = level.status
- else
- statusbar.main_txt = level.fullname
- end
- var t = new NextLevelButton(self)
- entities.push(t)
- run
- end
-
- # Play the next level.
- fun play_next
- do
- play(levels[level.number+1])
- end
-
-
- # Helper function to initialize all states.
- # Set up buttons for music and SFX.
- fun init_game
- do
- editing = false
- solver = null
- entities.clear
- entities.push(new MusicButton(self))
- entities.push(new SFXButton(self))
- entities.push(new MenuButton(self))
- statusbar.clear
- entities.push(statusbar)
- end
-
- # Helper function to initialize monster menu entries.
- fun init_play_menu(full: Bool)
- do
- init_game
- entities.push(board)
- entities.push(new ResetButton(self))
- entities.push(button_erase)
- # Push monster buttons and determine the selected one
- var sel: nullable Button = null
- for i in [1..monsters] do
- if grid.monsters[i].number > 0 or full then
- if selected_button == buttons[i] or sel == null then
- sel = buttons[i]
- end
- entities.push(buttons[i])
- end
- end
- selected_button = sel
- entities.push(score)
- end
-
- # Play a arbitrary grid in try mode.
- fun play_grid(g: Grid)
- do
- grid = g
- init_play_menu(false)
- statusbar.main_txt = "User level"
- if grid_edit != null then
- entities.push(new EditButton(self))
- end
- entities.push(new WonButton(self))
- run
- end
-
- # Launch the editor starting with a grid.
- fun edit_grid(g: Grid)
- do
- grid = g
- init_play_menu(true)
- editing = true
- statusbar.main_txt = "Level editor"
- if level != null then statusbar.main_txt += ": level "+level.name
- entities.push(button_wall)
- entities.push(button_size)
- entities.push(new TestButton(self))
- entities.push(new SaveButton(self))
- entities.push(new LoadButton(self))
- run
- end
-
- # Launch the title screen
- fun title
- do
- init_menu
- entities.push(new Splash(self))
- run
- end
-
- # Helper function to initialize the menu (and tile) screen
- fun init_menu
- do
- save # save the previous level grid
- init_game
- level = null
- var i = levels.first
- for l in levels do
- i = l
- if l.get_state == l.l_open then break
- end
- entities.push(new StartButton(self, i))
- end
-
- # Launch the menu.
- fun menu
- do
- init_menu
- var t
- t = new TextButton(self,"LEVEL SELECT", 120, ypad, "white", null, null)
- entities.push(t)
- for i in [0..levels.length[ do
- var b = new LevelButton(levels[i])
- entities.push(b)
- end
- t = new Achievement(self, 0, "Training")
- entities.push(t)
- t = new Achievement(self, 1, "Gold")
- entities.push(t)
- t = new Achievement(self, 2, "Editor")
- entities.push(t)
- t = new Achievement(self, 3, "Challenge")
- entities.push(t)
- t = new Achievement(self, 4, "Congraturation")
- entities.push(t)
- t = new Achievement(self, 5, "Awesome")
- entities.push(t)
- run
- end
-
- # Last function called when the lauch state is ready
- fun run do
- dirty_all = true
- end
-
- # Should all entity redrawn?
- var dirty_all = true
-
- # Draw all game entities.
- fun draw(display: Display) do
- dirty_all = true
- if dirty_all then display.blit(back, 0, 0)
- for g in entities do
- if g.dirty or dirty_all then
- g.dirty = false
- #if g.x2-g.x>0 and g.y2-g.y>0 then ctx.drawImage(back, g.x, g.y, g.x2-g.x, g.y2-g.y, g.x, g.y, g.x2-g.x, g.y2-g.y)
- g.draw(display)
- #ctx.rect(g.x, g.y, g.width, g.height)
- end
- end
- var ev = lastev
- if ev isa Event then
- # Cursor, kept for debugging
- #display.blit(img[4,0],ev.offset_x-42,ev.offset_y-6)
- end
- dirty_all = false
- end
-
- # Update all game entities.
- fun step do
- if solver != null and not solver_pause then
- if solver.run_steps(solver_steps) != null then solver_pause = true
- print solver.to_s
- if not solver.is_running then solver = null
- end
- for g in entities do
- g.update
- end
- end
-
- # Return the game entity located at a mouse event.
- fun get_game_element(ev: Event): nullable Entity
- do
- var x = ev.offset_x
- var y = ev.offset_y
- for g in entities do
- if x>=g.x and x<g.x2 and y>g.y and y<g.y2 then
- ev.game_x = x-g.x
- ev.game_y = y-g.y
- #print "get {g}"
- return g
- end
- end
- return null
- end
-
- # The game entlty the mouse went down on
- var drag: nullable Entity = null
-
- # Last mouse event. Used to dray the cursor
- var lastev: nullable Event = null
-
- # Callback when the mouse is pressed
- fun onMouseDown(ev: Event) do
- lastev = ev
- var g = get_game_element(ev)
- if g != null then
- g.click(ev)
- g.dirty = true
- end
- drag = g
- end
-
- # Callback when the mouse is releassed
- fun onMouseUp(ev: Event) do
- drag = null
- end
-
- # Callback when the mouse if moved while pressed
- fun onMouseMove(ev: Event) do
- lastev = ev
- var g = get_game_element(ev)
- if g == null then
- statusbar.dirty = true
- statusbar.over_txt = null
- statusbar.over_what = null
- return
- end
- if statusbar.over_what != g then
- statusbar.dirty = true
- var go = g.over
- statusbar.over_txt = go
- statusbar.over_what = g
- g.enter(ev)
- if go != null then print "***OVER*** {go}"
- end
- # We moved abode a element that accepts drag event
- if drag == g and g.draggable then
- # print "DRAG {g}"
- ev.drag = true
- g.click(ev)
- g.dirty = true
- end
- end
-
- # Current solver, if any
- var solver: nullable BacktrackSolver[Grid, Action] = null
-
- # Is the solver paused?
- var solver_pause = false
-
- # Number of solver steps played in a single game `update`
- var solver_steps = 20000
-
- # Callback when a keyboard event is recieved
- fun onKeyDown(ev: Event) do
- var kc = ev.char_code
- if kc == "e" then
- set_tmp("RUN EDITOR")
- grid_edit = grid.copy(true)
- edit_grid(grid)
- else if kc == "c" then
- if cheated then
- set_tmp("CHEAT: OFF")
- snd_duh.play
- cheated = false
- else
- set_tmp("CHEAT: ON")
- snd_win.play
- cheated = true
- end
- else if kc == "s" then
- if solver == null then
- solver = (new FriendzProblem(grid)).solve
- solver_pause = false
- else
- solver_pause = not solver_pause
- end
- if solver_pause then
- set_tmp("SOLVER: PAUSED")
- else
- set_tmp("SOLVER: ON")
- end
- #solver.step
- else if kc == "d" then
- if solver == null then
- solver = (new FriendzProblem(grid)).solve
- solver_pause = true
- set_tmp("SOLVER: ON")
- else
- solver_pause = true
- solver.run_steps(1)
- set_tmp("SOLVER: ONE STEP")
- end
- else if kc == "+" then
- solver_steps += 100
- set_tmp("SOLVER: {solver_steps} STEPS")
- else if kc == "-" then
- solver_steps -= 100
- set_tmp("SOLVER: {solver_steps} STEPS")
- else for g in entities do
- if kc == g.shortcut then
- g.click(ev)
- g.dirty = true
- end
- end
- end
-
- fun set_tmp(s: String)
- do
- statusbar.set_tmp(s, "cyan")
- end
-
- redef fun load_levels
- do
- super
-
- for level in levels do
- var score = app.data_store["s{level.str}"]
- if score isa Int then
- level.score = score
- end
- var saved_str = app.data_store["g{level.str}"]
- if saved_str isa String then
- print "LOAD {level.name}: {saved_str}"
- level.saved_str = saved_str
- end
- end
- end
-
- fun save
- do
- var l = level
- if l != null then
- l.save
- end
- end
-end
-
-# The spash title image
-class Splash
- super Entity
- init(game: Game)
- do
- super(game,game.xpad,game.ypad,380,350)
- end
- redef fun draw(ctx)
- do
- ctx.blit(game.logo, game.xpad, game.ypad)
- end
- redef fun click(ev)
- do
- game.snd_whip.play
- game.menu
- end
-end
-
-class NextLevelButton
- super TextButton
- init(game: Game)
- do
- super(game, "NEXT LEVEL", 440, 24, "cyan", "Play next level", null)
- enabled = false
- end
-
- redef fun update
- do
- var w = game.level.check_won(game.grid)
- if self.enabled != w then
- self.dirty = true
- self.enabled = w
- if w then
- game.snd_win.play
- game.statusbar.set_tmp("Level solved!", "cyan")
- end
- end
- end
-
- redef fun click(ev)
- do
- if not self.enabled then
- game.snd_duh.play
- var grid = game.grid
- var monsters = grid.monsters
- var angry = new Array[Tile]
- var lonely = new Array[Tile]
- var edges = new Array[Tile]
- for i in [0..grid.width[ do
- for j in [0..grid.height[ do
- var t = grid.grid[i][j]
- if t.kind == 0 then continue
- if t.nexts == 0 then lonely.push(t)
- if t.nexts == 1 and not monsters[t.kind].chain then edges.push(t)
- if t.nexts > 2 then angry.push(t)
- end
- end
-
- var l
- if angry.length>0 then
- l = angry
- else if lonely.length>0 then
- l = lonely
- else
- l = edges
- end
- for i in l do i.shocked=5
-
- if angry.length>0 then
- game.statusbar.set_tmp("Angry monsters!", "red")
- else if lonely.length>0 then
- game.statusbar.set_tmp("Lonely monsters!", "red")
- else if not grid.won then
- game.statusbar.set_tmp("Unconnected monsters!", "red")
- else
- game.statusbar.set_tmp("Not enough monsters!", "red")
- end
- return
- end
-
- game.snd_whip.play
- game.play_next
- end
-end
-
-class MusicButton
- super TextButton
- init(game: Game)
- do
- super(game, "MUSIC", 470, 412, "purple", "Mute the music", "Unmute the music")
- toggled = game.music_muted
- end
- redef fun click2(ev)
- do
- game.music_muted = self.toggled
- if game.music_muted then game.music.pause else game.music.play
- app.data_store["music_muted"] = game.music_muted
- end
-end
-
-class SFXButton
- super TextButton
- init(game: Game)
- do
- super(game, "SOUND FX", 470, 382, "purple", "Mute the sound effects", "Unmute the sound effects")
- toggled = game.sfx_muted
- end
-
- redef fun click2(ev)
- do
- game.sfx_muted = self.toggled
- if not game.sfx_muted then game.snd_whip.play # Because the automatic one was muted
- app.data_store["sfx_muted"] = game.sfx_muted
- end
-end
-
-class MenuButton
- super TextButton
- init(game: Game)
- do
- super(game, "MENU", 470, 442, "purple", "Exit to the main menu", null)
- shortcut = "back"
- end
-
- redef fun click2(ev)
- do
- game.menu
- end
-end
-
-class ResetButton
- super TextButton
- init(game: Game)
- do
- super(game,"RESET", 440, 61, "purple", "Clear the grid",null)
- end
-
- var count = 0
-
- redef fun click2(ev)
- do
- self.count += 1
- if self.count==1 then
- game.statusbar.set_tmp("Click again to reset","white")
- else if self.count==2 then
- game.grid.reset(false)
- if game.editing then
- game.statusbar.set_tmp("Click again to clear all","white")
- end
- else if game.editing then
- game.grid.reset(true)
- end
- game.dirty_all = true
- end
-
- redef fun enter2
- do
- self.count = 0
- end
-end
-
-class EditButton
- super TextButton
- init(game: Game)
- do
- super(game,"EDIT", 540, 24, "purple", "Return to the editor",null)
- end
-
- redef fun click2(ev)
- do
- var ge = game.grid_edit
- assert ge != null
- game.edit_grid(ge)
- end
-end
-
-class WonButton
- super TextButton
- init(game: Game)
- do
- super(game,"WON", 440, 24, "cyan", "", null)
- enabled = false
- end
- redef fun click2(ev)
- do
- var ge = game.grid_edit
- if not self.enabled then
- game.statusbar.set_tmp("Solve the level first!", "red")
- else if ge != null then
- game.snd_whip.play
- game.edit_grid(ge)
- else
- game.snd_whip.play
- game.menu
- end
- end
-
- redef fun update
- do
- var w = game.grid.won
- if self.enabled != w then
- self.dirty = true
- self.enabled = w
- if w then
- game.snd_win.play
- game.statusbar.set_tmp("Level solved!", "cyan")
- end
- end
- end
-end
-
-class TestButton
- super TextButton
- init(game: Game)
- do
- super(game,"TEST", 440, 24, "cyan", "Try to play the level", null)
- end
-
- redef fun click2(ev)
- do
- game.grid_edit = game.grid
- game.play_grid(game.grid.copy(false))
- end
-end
-
-class SaveButton
- super TextButton
- init(game: Game)
- do
- super(game, "SAVE", 540, 24, "purple", "Save the level", null)
- end
-
- redef fun click2(ev)
- do
- var res = game.grid.save
- print "SAVE: {res}"
- end
-end
-
-class LoadButton
- super TextButton
- init(game: Game)
- do
- super(game,"LOAD", 540, 61, "purple", "Load an user level", null)
- end
-
- redef fun click2(ev)
- do
- var grid2 = new Grid(game.gw,game.gh,game.monsters)
- if grid2.load("") then
- game.grid = grid2
- end
- game.dirty_all = true
- end
-end
-
-class ContinueButton
- super TextButton
- init(game: Game)
- do
- super(game,"CONTINUE", 440, 24, "purple", "Continue playing", null)
- end
-
- redef fun click2(ev)
- do
- game.play_next
- end
-end
-
-class StartButton
- super TextButton
- var level: Level
- init(game: Game, level: Level)
- do
- self.level = level
- if level.number == 0 then
- super(game,"START", 440, 24, "purple", "Play the first level", null)
- else
- super(game,"CONTINUE", 440, 24, "purple", "Continue from level "+level.name, null)
- end
- end
-
- redef fun click2(ev)
- do
- game.play(level)
- end
-end
-
-#
-
-redef class App
-
- # The game
- var game: Game
-
- # Wanted screen width
- var screen_width = 640
-
- # Wanted screen height
- var screen_height = 480
-
- redef fun on_create
- do
- super
- game = new Game
- game.font.hspace = -2
- if args.length > 0 then
- game.play(game.levels[args.first.to_i])
- end
- # img loading?
- end
-
- redef fun on_pause
- do
- super
- game.save
- end
-
- redef fun frame_core(display)
- do
- game.step
- game.draw(display)
- end
-
- redef fun input(input_event)
- do
- #print input_event
- if input_event isa QuitEvent then # close window button
- quit = true # orders system to quit
- else if input_event isa PointerEvent then
- var ev = new Event(input_event.x.to_i, input_event.y.to_i, "")
- if input_event.is_motion then
- game.onMouseMove(ev)
- else if input_event.pressed then
- game.onMouseDown(ev)
- else
- game.onMouseUp(ev)
- end
- return true
- else if input_event isa KeyEvent and input_event.is_down then
- var ev = new Event(0, 0, input_event.key_name)
- game.onKeyDown(ev)
- return true
- end
-
- return false
- end
-end
-
-redef class PointerEvent
- fun is_motion: Bool do return false
-end
-
-redef class KeyEvent
- fun key_name: String
- do
- var c = to_c
- if c != null then return c.to_s
- return "unknown"
- end
-end
-
-redef class Level
- # Save the score and grid of the level
- fun save
- do
- app.data_store["s{str}"] = if score > 0 then score else null
- var saved = game.grid.save
- saved_str = saved
- app.data_store["g{str}"] = saved
- print "SAVE: {name}: {saved}"
- end
-
- # The saved player grid (to continue games)
- var saved_str: nullable String = null
-end
+++ /dev/null
-# Monsterz - Chains of Friends
-#
-# 2010-2014 (c) Jean Privat <jean@pryen.org>
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the Do What The Fuck You Want To
-# Public License, Version 2, as published by Sam Hocevar. See
-# http://sam.zoy.org/projects/COPYING.WTFPL for more details.
-
-# Game login on the grid of monsters
-module grid
-
-# Grid of monsters.
-class Grid
- # width of the current grid
- var width: Int is noinit
-
- # maximum width of the grid
- var max_width: Int
-
- # height of the current grid
- var height: Int is noinit
-
- # maximum height of the grid
- var max_height: Int
-
- # nm is the number of monster + 1 for the empty tile
- var nb_monsters: Int
-
- # the data grid
- private var grid = new Array[Array[Tile]]
-
- init do clear
-
- # Reinitialize the grid with new empty tiles and monsters info
- fun clear
- do
- self.number = 0
- self.error = 0
- self.won = false
- for i in [0..max_width[ do
- self.grid[i] = new Array[Tile]
- for j in [0..max_height[ do
- var t = new Tile(self, i, j)
- self.grid[i][j] = t
- end
- end
- self.monsters = new Array[MonsterInfo]
- for i in [0..nb_monsters] do
- self.monsters[i] = new MonsterInfo
- end
- self.resize(max_width,max_height)
- end
-
- # Clear all monsters
- # if `fixed` is false, fixed monsters remains
- fun reset(fixed: Bool): Bool
- do
- var r = false
- for i in [0..width[ do
- for j in [0..height[ do
- var t = self.grid[i][j]
- if fixed then t.fixed = false
- if not t.fixed and t.kind>0 then
- t.update(0)
- r = true
- end
- end
- end
- return r
- end
-
- # Total number of monsters in the grid
- var number = 0
-
- # Number of monsters alone or with >=3 next
- var error = 0
-
- # information about each kind of monsters
- var monsters = new Array[MonsterInfo]
-
- # Last result of win.
- var won = false
-
- # Check that the monster of `kind` form a complete chain.
- # if not null, `t` is used as a starting tile to check the chain
- fun check_chain(kind: Int, t: nullable Tile): Bool
- do
- var m = monsters[kind]
- if m.angry > 0 or m.lonely > 0 or m.single > 2 then
- # easy case for no chain
- # print "easy {kind} chain: false"
- m.chain = false
- return false
- else
- if t == null then
- # Search for a starting
- for x in [0..width[ do for y in [0..height[ do
- var t2 = get(x,y)
- if t2 == null or t2.kind != kind then continue
- t = t2
- break label found
- end label found
- if t == null then
- assert m.number == 0
- m.chain = true
- return m.chain
- end
- # print "get neighbor {t}"
- end
- assert t.kind == kind
- var c = count_chain(t, 1000.rand)
- # print "old {kind} chain? {c} / {m.number}"
- m.chain = c == m.number
- return m.chain
- end
- end
-
- # The total number of monsters connected to the chain of `t`
- # `mark` is a uniq number used to mark tiles
- fun count_chain(t: Tile, mark: Int): Int
- do
- t.chain_mark = mark
- var res = 1
- for i in [-1..1] do
- for j in [-1..1] do
- if (i==0 and j==0) or (i!=0 and j!=0) then continue
- var t2 = get(t.x+i,t.y+j)
- if t2 == null then continue
- if t2.chain_mark == mark or t2.kind != t.kind then continue
- res += count_chain(t2, mark)
- end
- end
- return res
- end
-
- # Resize the grid. Do not touch the content.
- fun resize(w,h: Int)
- do
- self.width = w
- self.height = h
- end
-
- # Try to get the tila at `x`,`y`.
- # Returns null if the position is out of bound.
- fun get(x,y: Int): nullable Tile
- do
- if x<0 or x>=self.width or y<0 or y>=self.height then return null
- return self.grid[x][y]
- end
-
-
- var fixed_shaped = """[
- [{x:1,y:0},{x:2,y:0},{x:1,y:1},{x:2,y:1}],
- [{x:0,y:0},{x:0,y:1},{x:0,y:2}],
- [{x:1,y:2},{x:2,y:2},{x:3,y:2}],
- [{x:4,y:1},{x:4,y:2}],
- [{x:3,y:0},{x:4,y:0}],
- [{x:3,y:1}]
- ]"""
-
- # Set shapes for the fixed blocks.
- fun metalize
- do
- for i in [0..width[ do
- for j in [0..height[ do
- var t = self.grid[i][j]
- if t.fixed then t.shape = null
- end
- end
- for shape in fixed_shaped.split(",") do
- for i in [0..width[ do
- for j in [0..height[ do
- var ts = new Array[Tile]
- for l in [0..shape.length[ do
- #var t = self.get(i+shape[l].x-shape[0].x,j+shape[l].y-shape[0].y)
- var t = self.get(i,j)
- if t != null and t.fixed and t.shape == null then ts.push(t)
- end
- if ts.length == shape.length then
- for l in [0..shape.length[ do
- ts[l].shape = shape[l]
- end
- end
- end
- end
- end
- end
-
- # Return the serialization of the fixed tiles. */
- fun save: String
- do
- var res = ""
- var str = ".abcdefghi#ABCDEFGHI"
- for y in [0..height[ do
- var rle = 0
- var last: nullable Int = null
- for x in [0..width[ do
- var t = self.grid[x][y]
- var tk = t.kind
- if t.fixed then tk += 10
- if tk == last and rle<9 then
- rle += 1
- else
- if last != null then
- if rle>1 then res += rle.to_s
- res += str.chars[last].to_s
- end
- rle = 1
- last = tk
- end
- end
- if last != null then
- if rle>1 then res += rle.to_s
- res += str.chars[last].to_s
- end
- res += "|"
- end
- return res
- end
-
- # Load a new grid from a seialization.
- fun load(str: String): Bool
- do
- self.clear
- var l = str.length
- var x = 0
- var y = 0
- var mx = 1
- var my = 1
- var rle = 1
- for i in [0..l[ do
- var z = rle
- while z > 0 do
- z -= 1
- rle = 1
- var c = str.chars[i]
- if c == '|' then
- if x > mx then mx = x
- x = 0
- y += 1
- else if c == '.' then
- x += 1
- else if c == '#' then
- var t = self.get(x,y)
- assert t != null
- t.fixed = true
- x += 1
- else if c >= 'A' and c <= 'I' then
- var t = self.get(x,y)
- assert t != null
- t.update(c.code_point-'A'.code_point+1)
- t.fixed = true
- x += 1
- else if c >= 'a' and c <= 'i' then
- var t = self.get(x,y)
- assert t != null
- t.update(c.code_point-'a'.code_point+1)
- x += 1
- else if c >= '1' and c <= '9' then
- rle = c.to_i
- else
- abort
- end
- end
- end
- if x>0 then y += 1
- if x > mx then mx = x
- if y > my then my = y
- if mx<3 or my<3 or mx>max_width or my>max_height then
- return false
- end
- self.resize(mx,my)
- self.metalize
- return true
- end
-
- # A ASCII version of the grid.
- redef fun to_s: String
- do
- var ansicols = once ["37;1","31","36;1","32;1","35;1","33;1","33","34;1","31;1","37"]
- var b = new FlatBuffer
- b.append("{width}x{height}\n")
- for j in [0..height[ do
- for i in [0..width[ do
- var t = grid[i][j]
- var k = t.kind
- var c = ' '
- if k == 0 then
- if t.fixed then c = '#'
- else
- b.add(0x1b.code_point)
- b.add('[')
- b.append ansicols[k]
- c = (k + 'a'.code_point - 1).code_point
- if t.fixed then c = c.to_upper
- b.append("m")
- end
- b.add(c)
- if k != 0 then
- b.add(0x1b.code_point)
- b.append("[0m")
-
- end
- end
- b.append "|\n"
- end
- return b.to_s
- end
-
- # Return a copy of the current grid.
- # if (!no_fixed) copy only the fixed tiles.
- fun copy(no_fixed: Bool): Grid
- do
- var g = new Grid(self.max_width, self.max_height, self.nb_monsters)
- g.resize(width, height)
- for y in [0..height[ do
- for x in [0..width[ do
- var t = self.grid[x][y]
- if no_fixed or t.fixed then
- var t2 = g.grid[x][y]
- t2.update(t.kind)
- t2.fixed = t.fixed
- end
- end
- end
- g.metalize
- return g
- end
-
- # Internal check of the validity of tile and monster informations
- fun check_grid
- do
- var m2 = new Array[MonsterInfo]
- for m in [0..nb_monsters] do
- m2[m] = new MonsterInfo
- end
- for x in [0..width[ do
- for y in [0..height[ do
- var n = 0
- var f = 0
- var t = get(x,y)
- assert t != null
- assert t.x == x
- assert t.y == y
- var k = t.kind
- if k == 0 then continue
-
- for i in [-1..1] do
- for j in [-1..1] do
- if i == j or (i != 0 and j != 0) then continue
- var t2 = get(x+i, y+j)
- if t2 == null then continue
- if t2.kind == k then
- n += 1
- else if t2.kind == 0 and not t2.fixed then
- f += 1
- end
- end
- end
- assert n == t.nexts else
- print self
- print "{t} says {t.nexts} nexts, found {n}"
- end
- #assert f == t.frees else
-
- var m = m2[k]
- m.number += 1
- if n == 0 then
- m.lonely += 1
- else if n == 1 then
- m.single += 1
- else if n > 2 then
- m.angry += 1
- end
- end
- end
- for m in [1..nb_monsters] do
- assert m2[m].number == monsters[m].number
- assert m2[m].lonely == monsters[m].lonely
- assert m2[m].single == monsters[m].single
- assert m2[m].angry == monsters[m].angry
- end
- end
-end
-
-# Information about each kind of monsters
-class MonsterInfo
- # number of monsters of this kind on board
- var number = 0
- # number of monsters of this kind to place, -1 if no limit
- var remains: Int = -1
- # number of monsters that have exactly 1 next
- var single = 0
- # number of monsters that have exactly 0 next
- var lonely = 0
- # number of monsters that have 3 or more next
- var angry = 0
- # Are all monsters form a wining chain?
- var chain = false
-end
-
-# A localized tile of a grid, can contain a monster and be fixed.
-class Tile
- # The grid of the tile.
- var grid: Grid
-
- # The x coordinate in the grid (starting from 0).
- var x: Int
-
- # The y coordinate in the grid (starting from 0).
- var y: Int
-
- # The kind of monster in the grid. 0 means empty.
- var kind = 0
-
- # blink time to live (0 means no blinking).
- var blink = 0
-
- # shocked time to live (0 means not shocked)
- var shocked = 0
-
- # number of neighbors of the same kind.
- var nexts = 0
-
- # number of free non fixed next tiles
- var frees = 0
-
- # is the tile editable (metal block)
- var fixed = false
-
- redef fun to_s
- do
- var s
- if fixed then
- s = "#ABCDEFGHI"
- else
- s = ".abcdefghi"
- end
- return "\{{x},{y}:{s.chars[kind]}\}"
- end
-
- # Shape for metal block
- var shape: nullable Object = null
-
- # Flag for `count_chain` computation.
- private var chain_mark = 0
-
- # Set a new kind of monster on tile
- # Return true is the move made the grid unsolvable (bad move)
- fun update(nkind: Int): Bool
- do
- var t = self
- var g = self.grid
- var res = false
- var okind = t.kind
- if okind == nkind then return false
-
-
- # First, remove it and update info.
- if okind > 0 then
- var m = g.monsters[okind]
- var n = t.nexts
- if n > 2 then
- g.error -= 1
- m.angry -= 1
- else if n == 1 then
- m.single -= 1
- else if n == 0 then
- g.error -= 1
- m.lonely -= 1
- end
- m.number -= 1
- g.number -= 1
- end
- t.nexts = 0
- t.blink = 5
- t.frees = 0
-
- var a_neigbor: nullable Tile = null
- # update neighbors
- for i in [-1..1] do
- for j in [-1..1] do
- if (i==0 and j==0) or (i!=0 and j!=0) then continue
- var t2 = g.get(t.x+i,t.y+j)
- if t2 == null then continue
- if t2.kind == 0 then
- if not t2.fixed then t.frees += 1
- continue
- end
- var m = g.monsters[t2.kind]
-
- if t2.kind == okind then
- if a_neigbor == null then a_neigbor = t2
- # same than old, thus dec neighbors
- t2.nexts -=1
- var n = t2.nexts
- if n == 2 then
- g.error -= 1
- m.angry -= 1
- else if n == 1 then
- m.single += 1
- g.error += 1
- else if n == 0 then
- m.single -= 1
- m.lonely += 1
- end
- # print "+ {t} one less next: {t2} ; +({i}x{j})"
- end
-
- if t2.kind == nkind then
- # Same than new, thus inc neighbors
- t2.nexts += 1
- t.nexts += 1
- var n = t2.nexts
- if n > 3 then
- res = true
- else if n == 3 then
- g.error += 1
- m.angry += 1
- res = true
- else if n == 2 then
- m.single -= 1
- g.error -= 1
- else if n == 1 then
- m.single += 1
- m.lonely -= 1
- end
- # print "+ {t} one more next: {t2}"
- end
- end
- end
-
- # Add and update neighbors info
- t.kind = nkind
- if nkind > 0 then
- var m = g.monsters[nkind]
- var n = t.nexts
- if n > 2 then
- g.error += 1
- m.angry += 1
- else if n == 1 then
- m.single += 1
- g.error += 1
- else if n == 0 then
- g.error += 1
- m.lonely += 1
- end
- m.number+=1
- g.number+=1
-
- g.check_chain(nkind, t)
- end
-
- # check if the old kind broke, or create a chain
- if okind > 0 then
- g.check_chain(okind, a_neigbor)
- end
-
- # update win status
- g.won = true
- for m in g.monsters do
- if m.number > 0 and not m.chain then g.won = false
- end
-
- #grid.check_grid
-
- return res
- end
-end
-