From 32150473c6265dff9f0a93f9efb94091537c2c3b Mon Sep 17 00:00:00 2001 From: Nolan Casey Date: Wed, 14 May 2025 11:32:36 -0400 Subject: [PATCH] Added win/lose conditions, lose animation. Clicking on revealed tile with correct number of flags reveals the surrounding unflagged tiles. Added completion timer. Probably other things I'm forgetting --- .../scenes/Minefield.tscn1397283733.tmp | 9 ++++ .../scenes/Minefield.tscn1403038500.tmp | 9 ++++ minesweeper/scripts/Minefield.gd | 26 ++++++++++ minesweeper/scripts/Tile.gd | 48 +++++++++++++----- minesweeper/sprites/brokenFlag.png | Bin 0 -> 265 bytes minesweeper/sprites/brokenFlag.png.import | 34 +++++++++++++ minesweeper/sprites/minsweeperSprites.xcf | Bin 6227 -> 7838 bytes 7 files changed, 112 insertions(+), 14 deletions(-) create mode 100644 minesweeper/scenes/Minefield.tscn1397283733.tmp create mode 100644 minesweeper/scenes/Minefield.tscn1403038500.tmp create mode 100644 minesweeper/sprites/brokenFlag.png create mode 100644 minesweeper/sprites/brokenFlag.png.import diff --git a/minesweeper/scenes/Minefield.tscn1397283733.tmp b/minesweeper/scenes/Minefield.tscn1397283733.tmp new file mode 100644 index 0000000..f14de50 --- /dev/null +++ b/minesweeper/scenes/Minefield.tscn1397283733.tmp @@ -0,0 +1,9 @@ +[gd_scene load_steps=2 format=3 uid="uid://b8ewc3373b0t2"] + +[ext_resource type="Script" uid="uid://du63nf81skbpt" path="res://scripts/Minefield.gd" id="1_rm6t6"] + +[node name="Minefield" type="Node2D"] +script = ExtResource("1_rm6t6") + +[node name="Camera2D" type="Camera2D" parent="."] +zoom = Vector2(4, 4) diff --git a/minesweeper/scenes/Minefield.tscn1403038500.tmp b/minesweeper/scenes/Minefield.tscn1403038500.tmp new file mode 100644 index 0000000..f14de50 --- /dev/null +++ b/minesweeper/scenes/Minefield.tscn1403038500.tmp @@ -0,0 +1,9 @@ +[gd_scene load_steps=2 format=3 uid="uid://b8ewc3373b0t2"] + +[ext_resource type="Script" uid="uid://du63nf81skbpt" path="res://scripts/Minefield.gd" id="1_rm6t6"] + +[node name="Minefield" type="Node2D"] +script = ExtResource("1_rm6t6") + +[node name="Camera2D" type="Camera2D" parent="."] +zoom = Vector2(4, 4) diff --git a/minesweeper/scripts/Minefield.gd b/minesweeper/scripts/Minefield.gd index 1142963..c965648 100644 --- a/minesweeper/scripts/Minefield.gd +++ b/minesweeper/scripts/Minefield.gd @@ -11,8 +11,14 @@ static var instance: Minefield @export var startingSafeZone: int = 2 var tiles: Array[Tile] +var mines: Array[Tile] +var falseFlags: Array[Tile] var spriteWidth: int = 16 var fieldInitialized: bool = false +var gameRunning: bool = true +var revealed: int = 0 +var winCondition: int +var timer: int func _enter_tree() -> void: if (instance == null || is_instance_valid(instance)): instance = self @@ -20,6 +26,7 @@ func _enter_tree() -> void: func _ready() -> void: var scale: float = 1 + winCondition = (width * height) - mineCount for x: int in range(width): for y: int in range(height): var tile: Tile = tilePrefab.instantiate() @@ -38,10 +45,29 @@ func initField(coord: Vector2i) -> void: pickPool.erase(pick) if (abs(coord - pick.coord) >= Vector2i(startingSafeZone, startingSafeZone)): pick.type = Tile.TileTypes.MINE + mines.append(pick) break pick = pickPool.pick_random() for tile: Tile in tiles: tile.evaluateType() + timer = Time.get_ticks_msec() + +func lose() -> void: + gameRunning = false + for mine: Tile in mines: + await get_tree().create_timer(randf_range(0.01, 0.25)).timeout + mine.reveal() + for flag: Tile in falseFlags: + await get_tree().create_timer(randf_range(0.01, 0.1)).timeout + flag.breakFlag() + +func tilesRevealed(count: int) -> void: + revealed += count + if (revealed == winCondition): + gameRunning = false + var completionTime: int = Time.get_ticks_msec() - timer + var humanTime: float = float(completionTime) / 1000 + print("You win.\nCompleted in " + str(humanTime) + " seconds") func getTile(coord: Vector2i) -> Tile: if (coord.x < 0 || coord.y < 0 || coord.x >= width || coord.y >= width): return null diff --git a/minesweeper/scripts/Tile.gd b/minesweeper/scripts/Tile.gd index c5eef18..c5c9446 100644 --- a/minesweeper/scripts/Tile.gd +++ b/minesweeper/scripts/Tile.gd @@ -4,6 +4,7 @@ class_name Tile extends Node2D @onready var cover: CompressedTexture2D = preload("res://sprites/cover.png") @onready var mine: CompressedTexture2D = preload("res://sprites/mine.png") @onready var flag: CompressedTexture2D = preload("res://sprites/flag.png") +@onready var brokenFlag: CompressedTexture2D = preload("res://sprites/brokenFlag.png") @onready var nums: Array[CompressedTexture2D] = [ preload("res://sprites/1.png"), preload("res://sprites/2.png"), @@ -36,44 +37,63 @@ var coord: Vector2i var revealed: bool = false var flagged: bool = false -func evaluateType() -> void: - if (type == TileTypes.MINE): return - var count: int = 0 +func getNeighbors() -> Array[Tile]: + var results: Array[Tile] for x: int in range(-1, 2): for y: int in range(-1, 2): if (x == 0 && y == 0): continue var tile: Tile = Minefield.instance.getTile(coord + Vector2i(x, y)) if (tile == null): continue - if (tile.type == TileTypes.MINE): count += 1 + results.append(tile) + return results + +func evaluateType() -> void: + if (type == TileTypes.MINE): return + var count: int = 0 + for tile: Tile in getNeighbors(): + if (tile.type == TileTypes.MINE): count += 1 type = count as TileTypes -func reveal() -> void: - if (revealed): return +func reveal() -> int: + if (revealed || Minefield.instance.gameRunning && flagged): return 0 revealed = true + var count: int = 1 layer1.texture = backplate match type: TileTypes.EMPTY: layer2.texture = null - for x: int in range(-1, 2): - for y: int in range(-1, 2): - if (x == 0 && y == 0): continue - var tile: Tile = Minefield.instance.getTile(coord + Vector2i(x, y)) - if (tile == null): continue - tile.reveal() + for tile: Tile in getNeighbors(): count += tile.reveal() TileTypes.MINE: layer2.texture = mine + if (Minefield.instance.gameRunning): Minefield.instance.lose() _: layer2.texture = nums[type as int - 1] + return count + +func breakFlag() -> void: + layer2.texture = brokenFlag func _on_area_2d_input_event(viewport: Node, event: InputEvent, shape_idx: int) -> void: + if (!Minefield.instance.gameRunning): return if (event.is_released()): - if (event.is_action_released("reveal") && !flagged): + if (event.is_action_released("reveal") && revealed): + var count: int = 0 + var neighbors: Array[Tile] = getNeighbors() + for tile: Tile in neighbors: + if (tile.flagged): count += 1 + if (count == type as int): + count = 0 + for tile: Tile in neighbors: count += tile.reveal() + Minefield.instance.tilesRevealed(count) + elif (event.is_action_released("reveal") && !flagged): if (!Minefield.instance.fieldInitialized): Minefield.instance.initField(coord) - reveal() + Minefield.instance.tilesRevealed(reveal()) elif (event.is_action_released("flag") && !revealed): flagged = !flagged if (flagged): layer2.texture = flag + if (type != TileTypes.MINE): Minefield.instance.falseFlags.append(self) else: layer2.texture = null + if (type != TileTypes.MINE): Minefield.instance.falseFlags.erase(self) diff --git a/minesweeper/sprites/brokenFlag.png b/minesweeper/sprites/brokenFlag.png new file mode 100644 index 0000000000000000000000000000000000000000..b0dd66d846c1a2fde1eea37a0e88f0cd0f993fe9 GIT binary patch literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG z?e4-L27iPLuSp_SE9;_-Vz%#?LIwZ27%^V>*IS)Y)dwtz&SlF v$4}kLCU4`$zx$6K`!@fP#DXqG6LyA3Y1xMt_>bQM8qDD7>gTe~DWM4fWuZ*k literal 0 HcmV?d00001 diff --git a/minesweeper/sprites/brokenFlag.png.import b/minesweeper/sprites/brokenFlag.png.import new file mode 100644 index 0000000..2f76d04 --- /dev/null +++ b/minesweeper/sprites/brokenFlag.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d353xpjsitdct" +path="res://.godot/imported/brokenFlag.png-785bad89b513c2a3f4b8d504b0f83440.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://sprites/brokenFlag.png" +dest_files=["res://.godot/imported/brokenFlag.png-785bad89b513c2a3f4b8d504b0f83440.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/minesweeper/sprites/minsweeperSprites.xcf b/minesweeper/sprites/minsweeperSprites.xcf index 90a89e8580dc77c6e8d3228a6250f818f3a169a9..b3b6cda2c322972f5f9080b44b8c1be9219d3936 100644 GIT binary patch literal 7838 zcmeHMO>7(25q|vfS|UZuQe>K})n?1opi`8xWVf~p%Q7lKat)+_;n-1vNnpJP;YvrnbcY0<!#zOF332(&@>IttmyAq-l~4>`uA>-<+yaU>PF2h z)tXj?bnuS7eWvBzvrWC_+E(S&)R}6n?AmTKrK6mYqF%UJyjGkqen+pk?G@WB=;zPr zg?FZ3;CKGQ^g9|IEvS^*j^*i= zQ(iS&@L6-K)t2dD=I{^6N{l*Fh`(_S2;FG9WwX+5nhUN|wQ4upPN{0yW~t%SO6FR_ zZF(i!Xfc4X;u*{F%%*Lik?yz{2?#fuW~*hnj{a@bSMJm*`aL-z8dy0sR`0a}rDxU6 zKx`Rz&63wN9NbS6S)zR123)N7F$KdmJH0sytX@ zHN0|EvxXd(b7dEqJEca`rCW`fyw!Gn#W5^9st)p4DZ`nq7Y?K}rbpqO2xWEM^o)w( z8KkGo{>kjswYsg}HJcQ-E5(_q>7s5rWw!#vuM}_JS}I*CUY$$La>F?!{j6uybjPTh zR|<=`>#6IsV5a6RuXV$0zHZbT$p5)ZI)1Zf;d*!z+3+>fsd=k~xtR;kbNYReWSY@yuTpAN`}^V?S2>r4_{|{;YWRf#SJU#lJ**B+99e6^~qb z)1@skJ>PWin9h=I)W|0uIBMXu-TI)fl6>_8T(KpCNKZ`*fea=lLo%f!`B2e8EPp*D zr$h2cNX~@hQOGnNm!lA!9$f!dXDw;Y_LK=u1l3=ddZ$f6a zL8bkk^@}3D>t}~W64#VS;5s7qce8ZO3TR^4m>~O;?C!3JXOE&1-%VzRL~M6d_`5}c z4m8;TuIK<)G}ZcbplRHJCOgp3f%b3c;635{8L{sRAJg?QU4K}_5yiNFR1Eo;sgHsD zBHb~EfM7pT4RYmKIYJHpTXN)GnryEO{kA7}B@RN6BSXQGlyHz8kP_ddddmWh4d;4ISj&CPAY5RY7@E>B60t5_++>Pp%V-hTbl(V-u&lJymH(Hl9 z4nmN#nM%lqZBS)?!1}gGE~kXQ9RJh@f4n@q{Fmj5cv|Z(i$2<%)ZP-05Ptf@xhMCP zVWEX&wjS77;+JR=gM`p^j;i9vK8x06jZp{?8m0T^!Zy@LzrxBlMBGXX--?5Pl;r6{ zbc^CY?2R+R_c0UTtT<-USGP#h=lh{08NW!HK|=c~-B7O_|5H!yN*sXz?c;y%T6)oe zYK)$CWw|YqlQ@$nGoUahc}cvWH8|<{#8;f20G4QMo~uOb0%-;bttq;pUK#(eCwC=| zK!DbG@cJa}Y(r(d`y7bXreQ^BSP>dlgmw&%?BP1;u|hPtF4tjMhh`m~>+p=!Ne|C; z>A3;V4QMvtxdG2eo%HbBke-pX2hT_yo~XzDgKP0ybW4MT?#mLR?qwo;?y;5;4A7nU zfDPbmgE;XE)?>BZ2Lku8LO_Z8c=3@kUd-+<3Osg5f|uM4X&n&)^m;@xy!u4`o7DLr zMLx&TQvAfH(Yma0bc4vB_&nspHmI`WtiLUuVC_9fGfO-`L?g6_z3>U%_fNw|vBYWk zL`eEP6yhO#6iYmWPlTioJ-on2v4ljA5M(;Ie$LZG2MNV)( z*k!~sLioOOd6BI1u+GC8Az0_7bsyG!SR(}MzO(|FSa(+>#E9t2(SaQ@HE)#fG;I8v zjV3los0B!HyeI#_QiwA$f$wDaJ&f315wD dWerK2U0r{vgBFjsg&b+&MWc**%!jiljYBauCt46l(Y6 z#>FOWlBFcczvLhXNlT7$l9KUzW`}wG-e;Qcr|)}{ZoR2r>d;M>%Vk)@TfryB0+#%{jt+W#mqbk9y;g2Ruv`23@Szj6=N6jl9BaQFLK{@`=SE?