If the code for each zone does the exact same thing then it makes more sense to parameterize the code and have only one block instead of four. Then you will fix each mistake in one place instead of four places.
If this were my project I would probably store the info for each zone in an array of structures like this:
typedef struct {
int sensorPin; // soil sensor
int valvePin; // water valve
int reading; // mapped value
int lcd_row; // LCD coordinates for reading
int lcd_col;
} zone;
zone zoneInfo[] = {{A0, 2, 0, 0, 0}, {A1, 3, 0, 0, 9}, {A2, 4, 0, 1, 0}, {A3, 5, 0, 1, 9}};
const int NUM_ZONES = 4; // # of zones to process
byte zoneIdx = 0; // index to current zone
Each time through loop() I would only process one zone and would start with the value of NUM_ZONES set to 1 to test the first zone. When that zone was working it would be set to 2 to test the first two zones and so on.
With all the data held in an array and only one zone processed at a time the amount of code in the loop() function would be reduced.
void loop()
{
//read the value from the soil moisture sensor
int moistureValue = analogRead(zoneInfo[zoneIdx].sensorPin);
if ( moistureValue <= potLow )
{
//open the valve
digitalWrite(zoneInfo[zoneIdx].valvePin, HIGH);
} else if ( moistureValue >= potHigh ) {
//close the valve
digitalWrite(zoneInfo[zoneIdx].valvePin, LOW);
} // else
//map zone range to 0-99
zoneInfo[zoneIdx].reading = map(moistureValue, 0, 1023, 0, 99);
//set the cursor
lcd.setCursor(zoneInfo[zoneIdx].lcd_col, zoneInfo[zoneIdx].lcd_row);
//print mapped value of zone1
lcd.print(zoneInfo[zoneIdx].reading);
// increment zone index, wrap to zero
++zoneIdx %= NUM_ZONES;
}