1 /*
2     SPDX-FileCopyrightText: 2009 Ian Wadham <iandw.au@gmail.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "kgrlevelgrid.h"
8 
KGrLevelGrid(QObject * parent,const KGrRecording * theLevelData)9 KGrLevelGrid::KGrLevelGrid (QObject * parent, const KGrRecording * theLevelData)
10     :
11     QObject     (parent)
12 {
13     // Put a concrete wall all round the layout: left, right, top and bottom.
14     // This saves ever having to test for being at the edge of the layout.
15     int inWidth      = theLevelData->width;
16     width            = inWidth + ConcreteWall * 2;
17 
18     int inHeight     = theLevelData->height;
19     height           = inHeight + ConcreteWall * 2;
20 
21     int size         = width * height;
22 
23     layout.fill      (CONCRETE, size);
24 
25     // Initialise the flags for each cell.
26     heroAccess.fill  (0, size);
27     enemyAccess.fill (0, size);
28     enemyHere.fill   (-1, size);
29 
30     // Copy the cells of the layout, but enclosed within the concrete wall.
31     int inRow  = 0;
32     int outRow = width + ConcreteWall;
33 
34     for (int j = 0; j < inHeight; j++) {
35         for (int i = 0; i < inWidth; i++) {
36             char type = theLevelData->layout [inRow + i];
37             switch (type) {
38             case HLADDER:
39                 // Change hidden ladders to FREE, but keep a list of them.
40                 hiddenLadders.append (outRow + i);
41                 type = FREE;
42                 break;
43             case HENEMY:
44                 // Change hidden enemies to BRICK, but keep a list of them.
45                 hiddenEnemies.append (outRow + i);
46                 type = BRICK;
47                 break;
48             case FLASHING:
49                 // Change flashing nuggets to NUGGET, but keep a list of them.
50                 flashingGold.append (outRow + i);
51                 type = NUGGET;
52                 break;
53             }
54             layout [outRow + i] = type;
55         }
56         inRow  = inRow  + inWidth;
57         outRow = outRow + width;
58     }
59 }
60 
~KGrLevelGrid()61 KGrLevelGrid::~KGrLevelGrid()
62 {
63 }
64 
65 // Inline functions (see kgrlevelgrid.h).
66 //     char cellType   (int i, int j)
67 //     char heroMoves  (int i, int j)
68 //     char enemyMoves (int i, int j)
69 //     void gotGold    (const int i, const int j, const bool runnerHasGold)
70 //
71 //     void setEnemyOccupied (int i, int j, const int spriteId)
72 //     int enemyOccupied (int i, int j)
73 //
74 //     int  index      (int i, int j)
75 
calculateAccess(bool pRunThruHole)76 void KGrLevelGrid::calculateAccess (bool pRunThruHole)
77 {
78     runThruHole = pRunThruHole;		// Save a copy of the runThruHole rule.
79 
80     char here;
81     bool canEnter;
82 
83     // Calculate which cells can be entered (N.B. USEDHOLE is a trapped enemy).
84     for (int j = 1; j < height - 1; j++) {
85         for (int i = 1; i < width - 1; i++) {
86             here      = cellType (i, j);
87             canEnter  = (here != BRICK)  && (here != CONCRETE) &&
88                         (here != FBRICK) && (here != USEDHOLE);
89             heroAccess  [index (i, j)] = canEnter ? ENTERABLE : 0;
90             enemyAccess [index (i, j)] = canEnter ? ENTERABLE : 0;
91         }
92     }
93 
94     // Calculate the access *from* each cell to its neighbours.
95     for (int j = 1; j < height - 1; j++) {
96         for (int i = 1; i < width - 1; i++) {
97             calculateCellAccess (i, j);
98         }
99     }
100 }
101 
changeCellAt(const int i,const int j,const char type)102 void KGrLevelGrid::changeCellAt (const int i, const int j, const char type)
103 {
104     int  position          = index (i, j);
105     bool canEnter          = (type != BRICK) && (type != CONCRETE) &&
106                              (type != FBRICK) && (type != USEDHOLE);
107     layout      [position] = type;
108     heroAccess  [position] = canEnter ? ENTERABLE : 0;
109     enemyAccess [position] = canEnter ? ENTERABLE : 0;
110 
111     calculateCellAccess (i, j);		// Recalculate access *from* this cell
112 					// and access *to* it
113     calculateCellAccess (i, j - 1);	// from above,
114     calculateCellAccess (i - 1, j);	// from left,
115     calculateCellAccess (i + 1, j);	// from right,
116     calculateCellAccess (i, j + 1);	// and from below.
117 }
118 
calculateCellAccess(const int i,const int j)119 void KGrLevelGrid::calculateCellAccess (const int i, const int j)
120 {
121     Flags access = 0;
122     char  here   = cellType (i, j);
123     if (here == CONCRETE) {
124         // If edge-cell or other CONCRETE, no calculation (avoid index errors).
125         return;
126     }
127     char  below  = cellType (i, j + 1);
128 
129     access = heroMoves (i, j) & ENTERABLE;
130 
131     // Cannot enter brick, concrete or used hole: can drop into a false brick.
132     if (! (access & ENTERABLE) && (here != FBRICK)) {
133 	access = 0;
134     }
135     // If can stand or hang on anything, allow down, left and right.
136     else if ((below == BRICK) || (below == CONCRETE) || (below == USEDHOLE) ||
137 	(below == LADDER) || (here == LADDER) || (here == BAR)) {
138 	access |= (dFlag [STAND] | dFlag [DOWN] |
139 		   dFlag [LEFT]  | dFlag [RIGHT]);
140     }
141     // If cannot stand or hang, can go down (space or false brick) or
142     // maybe left or right (when standing on an enemy).
143     else {
144 	access |= (dFlag [DOWN] | dFlag [LEFT]  | dFlag [RIGHT]);
145     }
146     // Can only go up if there is a ladder here.
147     if (here == LADDER) {
148 	access |= dFlag [UP];
149     }
150 
151     // Mask out directions that are blocked above, below, L or R, but not for
152     // concrete/brick at edge of grid (or elsewhere) to avoid indexing errors.
153     if (access != 0) {
154         if (! (heroMoves (i, j - 1) & ENTERABLE)) {
155             access = ~dFlag [UP] & access;		// Cannot go up.
156         }
157         if (! (heroMoves (i - 1, j) & ENTERABLE)) {
158             access = ~dFlag [LEFT] & access;		// Cannot go left.
159         }
160         if (! (heroMoves (i + 1, j) & ENTERABLE)) {
161             access = ~dFlag [RIGHT] & access;		// Cannot go right.
162         }
163         if (! (heroMoves (i, j + 1) & ENTERABLE)) {
164             if (below != FBRICK) {
165                 access = ~dFlag [DOWN] & access;	// Cannot go down.
166             }
167         }
168     }
169 
170     heroAccess [index (i, j)]  = access;
171 
172     // Enemy access is the same as the hero's when no holes are open.
173     enemyAccess [index (i, j)] = heroAccess [index (i, j)];
174 
175     if (here == USEDHOLE) {
176         enemyAccess [index (i, j)] = UP;	// Can only climb out of hole.
177     }
178     else if (! runThruHole) {			// Check the rule.
179         char mask;
180         mask = (cellType (i - 1, j) == HOLE) ? dFlag [LEFT] : 0;
181         mask = (cellType (i + 1, j) == HOLE) ? (dFlag [RIGHT] | mask) : mask;
182         enemyAccess [index (i, j)] &= ~mask;	// Block access to holes at L/R.
183     }
184 }
185 
placeHiddenLadders()186 void KGrLevelGrid::placeHiddenLadders()
187 {
188     for (const int &offset : std::as_const(hiddenLadders)) {
189         int i = offset % width;
190         int j = offset / width;
191         changeCellAt (i, j, LADDER);
192     }
193     Q_EMIT showHiddenLadders (hiddenLadders, width);
194     hiddenLadders.clear();
195 }
196 
197 
198